]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/lib/OpenILS/Application/SuperCat.pm
Merge remote branch 'working/collab/phasefx/unified_vol_copy_ui_from_item_status_ui'
[Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / Application / SuperCat.pm
1 # We'll be working with XML, so...
2 use XML::LibXML;
3 use XML::LibXSLT;
4 use Unicode::Normalize;
5
6 # ... and this has some handy common methods
7 use OpenILS::Application::AppUtils;
8
9 my $parser = new XML::LibXML;
10 my $U = 'OpenILS::Application::AppUtils';
11
12
13 package OpenILS::Application::SuperCat;
14
15 use strict;
16 use warnings;
17 use OpenILS::Utils::Normalize qw( naco_normalize );
18
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/;
23
24 # This is the client class, used for connecting to open-ils.storage
25 use OpenSRF::AppSession;
26
27 # This is an extension of Error.pm that supplies some error types to throw
28 use OpenSRF::EX qw(:try);
29
30 # This is a helper class for querying the OpenSRF Settings application ...
31 use OpenSRF::Utils::SettingsClient;
32
33 # ... and here we have the built in logging helper ...
34 use OpenSRF::Utils::Logger qw($logger);
35
36 # ... and this is our OpenILS object (en|de)coder and psuedo-ORM package.
37 use OpenILS::Utils::Fieldmapper;
38
39 use OpenILS::Utils::CStoreEditor q/:funcs/;
40
41
42 our (
43   $_parser,
44   $_xslt,
45   %record_xslt,
46   %metarecord_xslt,
47   %holdings_data_cache,
48   %authority_browse_axis_cache,
49 );
50
51 sub child_init {
52         # we need an XML parser
53         $_parser = new XML::LibXML;
54
55         # and an xslt parser
56         $_xslt = new XML::LibXSLT;
57
58         # parse the MODS xslt ...
59         my $mods33_xslt = $_parser->parse_file(
60                 OpenSRF::Utils::SettingsClient
61                         ->new
62                         ->config_value( dirs => 'xsl' ).
63                 "/MARC21slim2MODS33.xsl"
64         );
65         # and stash a transformer
66         $record_xslt{mods33}{xslt} = $_xslt->parse_stylesheet( $mods33_xslt );
67         $record_xslt{mods33}{namespace_uri} = 'http://www.loc.gov/mods/v3';
68         $record_xslt{mods33}{docs} = 'http://www.loc.gov/mods/';
69         $record_xslt{mods33}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-3.xsd';
70
71         # parse the MODS xslt ...
72         my $mods32_xslt = $_parser->parse_file(
73                 OpenSRF::Utils::SettingsClient
74                         ->new
75                         ->config_value( dirs => 'xsl' ).
76                 "/MARC21slim2MODS32.xsl"
77         );
78         # and stash a transformer
79         $record_xslt{mods32}{xslt} = $_xslt->parse_stylesheet( $mods32_xslt );
80         $record_xslt{mods32}{namespace_uri} = 'http://www.loc.gov/mods/v3';
81         $record_xslt{mods32}{docs} = 'http://www.loc.gov/mods/';
82         $record_xslt{mods32}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-2.xsd';
83
84         # parse the MODS xslt ...
85         my $mods3_xslt = $_parser->parse_file(
86                 OpenSRF::Utils::SettingsClient
87                         ->new
88                         ->config_value( dirs => 'xsl' ).
89                 "/MARC21slim2MODS3.xsl"
90         );
91         # and stash a transformer
92         $record_xslt{mods3}{xslt} = $_xslt->parse_stylesheet( $mods3_xslt );
93         $record_xslt{mods3}{namespace_uri} = 'http://www.loc.gov/mods/v3';
94         $record_xslt{mods3}{docs} = 'http://www.loc.gov/mods/';
95         $record_xslt{mods3}{schema_location} = 'http://www.loc.gov/standards/mods/v3/mods-3-1.xsd';
96
97         # parse the MODS xslt ...
98         my $mods_xslt = $_parser->parse_file(
99                 OpenSRF::Utils::SettingsClient
100                         ->new
101                         ->config_value( dirs => 'xsl' ).
102                 "/MARC21slim2MODS.xsl"
103         );
104         # and stash a transformer
105         $record_xslt{mods}{xslt} = $_xslt->parse_stylesheet( $mods_xslt );
106         $record_xslt{mods}{namespace_uri} = 'http://www.loc.gov/mods/';
107         $record_xslt{mods}{docs} = 'http://www.loc.gov/mods/';
108         $record_xslt{mods}{schema_location} = 'http://www.loc.gov/standards/mods/mods.xsd';
109
110         # parse the ATOM entry xslt ...
111         my $atom_xslt = $_parser->parse_file(
112                 OpenSRF::Utils::SettingsClient
113                         ->new
114                         ->config_value( dirs => 'xsl' ).
115                 "/MARC21slim2ATOM.xsl"
116         );
117         # and stash a transformer
118         $record_xslt{atom}{xslt} = $_xslt->parse_stylesheet( $atom_xslt );
119         $record_xslt{atom}{namespace_uri} = 'http://www.w3.org/2005/Atom';
120         $record_xslt{atom}{docs} = 'http://www.ietf.org/rfc/rfc4287.txt';
121
122         # parse the RDFDC xslt ...
123         my $rdf_dc_xslt = $_parser->parse_file(
124                 OpenSRF::Utils::SettingsClient
125                         ->new
126                         ->config_value( dirs => 'xsl' ).
127                 "/MARC21slim2RDFDC.xsl"
128         );
129         # and stash a transformer
130         $record_xslt{rdf_dc}{xslt} = $_xslt->parse_stylesheet( $rdf_dc_xslt );
131         $record_xslt{rdf_dc}{namespace_uri} = 'http://purl.org/dc/elements/1.1/';
132         $record_xslt{rdf_dc}{schema_location} = 'http://purl.org/dc/elements/1.1/';
133
134         # parse the SRWDC xslt ...
135         my $srw_dc_xslt = $_parser->parse_file(
136                 OpenSRF::Utils::SettingsClient
137                         ->new
138                         ->config_value( dirs => 'xsl' ).
139                 "/MARC21slim2SRWDC.xsl"
140         );
141         # and stash a transformer
142         $record_xslt{srw_dc}{xslt} = $_xslt->parse_stylesheet( $srw_dc_xslt );
143         $record_xslt{srw_dc}{namespace_uri} = 'info:srw/schema/1/dc-schema';
144         $record_xslt{srw_dc}{schema_location} = 'http://www.loc.gov/z3950/agency/zing/srw/dc-schema.xsd';
145
146         # parse the OAIDC xslt ...
147         my $oai_dc_xslt = $_parser->parse_file(
148                 OpenSRF::Utils::SettingsClient
149                         ->new
150                         ->config_value( dirs => 'xsl' ).
151                 "/MARC21slim2OAIDC.xsl"
152         );
153         # and stash a transformer
154         $record_xslt{oai_dc}{xslt} = $_xslt->parse_stylesheet( $oai_dc_xslt );
155         $record_xslt{oai_dc}{namespace_uri} = 'http://www.openarchives.org/OAI/2.0/oai_dc/';
156         $record_xslt{oai_dc}{schema_location} = 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd';
157
158         # parse the RSS xslt ...
159         my $rss_xslt = $_parser->parse_file(
160                 OpenSRF::Utils::SettingsClient
161                         ->new
162                         ->config_value( dirs => 'xsl' ).
163                 "/MARC21slim2RSS2.xsl"
164         );
165         # and stash a transformer
166         $record_xslt{rss2}{xslt} = $_xslt->parse_stylesheet( $rss_xslt );
167
168         # parse the FGDC xslt ...
169         my $fgdc_xslt = $_parser->parse_file(
170                 OpenSRF::Utils::SettingsClient
171                         ->new
172                         ->config_value( dirs => 'xsl' ).
173                 "/MARC21slim2FGDC.xsl"
174         );
175         # and stash a transformer
176         $record_xslt{fgdc}{xslt} = $_xslt->parse_stylesheet( $fgdc_xslt );
177         $record_xslt{fgdc}{docs} = 'http://www.fgdc.gov/metadata/csdgm/index_html';
178         $record_xslt{fgdc}{schema_location} = 'http://www.fgdc.gov/metadata/fgdc-std-001-1998.xsd';
179
180         register_record_transforms();
181
182         return 1;
183 }
184
185 sub register_record_transforms {
186         for my $type ( keys %record_xslt ) {
187                 __PACKAGE__->register_method(
188                         method    => 'retrieve_record_transform',
189                         api_name  => "open-ils.supercat.record.$type.retrieve",
190                         api_level => 1,
191                         argc      => 1,
192                         signature =>
193                                 { desc     => "Returns the \U$type\E representation ".
194                                               "of the requested bibliographic record",
195                                   params   =>
196                                         [
197                                                 { name => 'bibId',
198                                                   desc => 'An OpenILS biblio::record_entry id',
199                                                   type => 'number' },
200                                         ],
201                                 'return' =>
202                                         { desc => "The bib record in \U$type\E",
203                                           type => 'string' }
204                                 }
205                 );
206
207                 __PACKAGE__->register_method(
208                         method    => 'retrieve_isbn_transform',
209                         api_name  => "open-ils.supercat.isbn.$type.retrieve",
210                         api_level => 1,
211                         argc      => 1,
212                         signature =>
213                                 { desc     => "Returns the \U$type\E representation ".
214                                               "of the requested bibliographic record",
215                                   params   =>
216                                         [
217                                                 { name => 'isbn',
218                                                   desc => 'An ISBN',
219                                                   type => 'string' },
220                                         ],
221                                 'return' =>
222                                         { desc => "The bib record in \U$type\E",
223                                           type => 'string' }
224                                 }
225                 );
226         }
227 }
228
229 sub tree_walker {
230         my $tree = shift;
231         my $field = shift;
232         my $filter = shift;
233
234         return unless ($tree && ref($tree->$field));
235
236         my @things = $filter->($tree);
237         for my $v ( @{$tree->$field} ){
238                 push @things, $filter->($v);
239                 push @things, tree_walker($v, $field, $filter);
240         }
241         return @things
242 }
243
244 # find a label_sortkey for a call number with a label which is equal
245 # (or close to) a given label value
246 sub _label_sortkey_from_label {
247         my ($label, $_storage, $ou_ids, $cp_filter) = @_;
248
249         my $closest_cn = $_storage->request(
250                         "open-ils.cstore.direct.asset.call_number.search.atomic",
251                         { label      => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label] } },
252                           owning_lib => $ou_ids,
253                           deleted    => 'f',
254                           @$cp_filter
255                         },
256                         { limit     => 1,
257                           order_by  => { acn => "oils_text_as_bytea(label), id" }
258                         }
259                 )->gather(1);
260         if (@$closest_cn) {
261                 return $closest_cn->[0]->label_sortkey;
262         } else {
263                 return '~~~'; #fallback to high ascii value, we are at the end
264         }
265 }
266
267 sub cn_browse {
268         my $self = shift;
269         my $client = shift;
270
271         my $label = shift;
272         my $ou = shift;
273         my $page_size = shift || 9;
274         my $page = shift || 0;
275         my $statuses = shift || [];
276         my $copy_locations = shift || [];
277
278         my ($before_limit,$after_limit) = (0,0);
279         my ($before_offset,$after_offset) = (0,0);
280
281         if (!$page) {
282                 $before_limit = $after_limit = int($page_size / 2);
283                 $after_limit += 1 if ($page_size % 2);
284         } else {
285                 $before_offset = $after_offset = int($page_size / 2);
286                 $before_offset += 1 if ($page_size % 2);
287                 $before_limit = $after_limit = $page_size;
288         }
289
290         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
291
292         my $o_search = { shortname => $ou };
293         if (!$ou || $ou eq '-') {
294                 $o_search = { parent_ou => undef };
295         }
296
297         my $orgs = $_storage->request(
298                 "open-ils.cstore.direct.actor.org_unit.search",
299                 $o_search,
300                 { flesh         => 100,
301                   flesh_fields  => { aou        => [qw/children/] }
302                 }
303         )->gather(1);
304
305         my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
306
307         $logger->debug("Searching for CNs at orgs [".join(',',@ou_ids)."], based on $ou");
308
309         my @list = ();
310
311     my @cp_filter = ();
312     if (@$statuses || @$copy_locations) {
313         @cp_filter = (
314             '-exists' => {
315                 from  => 'acp',
316                                 where => {
317                     call_number => { '=' => { '+acn' => 'id' } },
318                     deleted     => 'f',
319                     ((@$statuses)       ? ( status   => $statuses)       : ()),
320                                     ((@$copy_locations) ? ( location => $copy_locations) : ())
321                 }
322             }
323         );
324     }
325
326         my $label_sortkey = _label_sortkey_from_label($label, $_storage, \@ou_ids, \@cp_filter);
327
328         if ($page <= 0) {
329                 my $before = $_storage->request(
330                         "open-ils.cstore.direct.asset.call_number.search.atomic",
331                         { label_sortkey => { "<" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
332                           owning_lib    => \@ou_ids,
333               deleted => 'f',
334               @cp_filter
335                         },
336                         { flesh         => 1,
337                           flesh_fields  => { acn => [qw/record owning_lib prefix suffix/] },
338                           order_by      => { acn => "oils_text_as_bytea(label_sortkey) desc, oils_text_as_bytea(label) desc, id desc, owning_lib desc" },
339                           limit         => $before_limit,
340                           offset        => abs($page) * $page_size - $before_offset,
341                         }
342                 )->gather(1);
343                 push @list, reverse(@$before);
344         }
345
346         if ($page >= 0) {
347                 my $after = $_storage->request(
348                         "open-ils.cstore.direct.asset.call_number.search.atomic",
349                         { label_sortkey => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
350                           owning_lib    => \@ou_ids,
351               deleted => 'f',
352               @cp_filter
353                         },
354                         { flesh         => 1,
355                           flesh_fields  => { acn => [qw/record owning_lib prefix suffix/] },
356                           order_by      => { acn => "oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib" },
357                           limit         => $after_limit,
358                           offset        => abs($page) * $page_size - $after_offset,
359                         }
360                 )->gather(1);
361                 push @list, @$after;
362         }
363
364         return \@list;
365 }
366 __PACKAGE__->register_method(
367         method    => 'cn_browse',
368         api_name  => 'open-ils.supercat.call_number.browse',
369         api_level => 1,
370         argc      => 1,
371         signature =>
372                 { desc     => <<"                 DESC",
373 Returns the XML representation of the requested bibliographic record's holdings
374                   DESC
375                   params   =>
376                         [
377                                 { name => 'label',
378                                   desc => 'The target call number label',
379                                   type => 'string' },
380                                 { name => 'org_unit',
381                                   desc => 'The org unit shortname (or "-" or undef for global) to browse',
382                                   type => 'string' },
383                                 { name => 'page_size',
384                                   desc => 'Count of call numbers to retrieve, default is 9',
385                                   type => 'number' },
386                                 { name => 'page',
387                                   desc => 'The page of call numbers to retrieve, calculated based on page_size.  Can be positive, negative or 0.',
388                                   type => 'number' },
389                                 { name => 'statuses',
390                                   desc => 'Array of statuses to filter copies by, optional and can be undef.',
391                                   type => 'array' },
392                                 { name => 'locations',
393                                   desc => 'Array of copy locations to filter copies by, optional and can be undef.',
394                                   type => 'array' },
395                         ],
396                   'return' =>
397                         { desc => 'Call numbers with owning_lib and record fleshed',
398                           type => 'array' }
399                 }
400 );
401
402 sub cn_startwith {
403         my $self = shift;
404         my $client = shift;
405
406         my $label = shift;
407         my $ou = shift;
408         my $limit = shift || 10;
409         my $page = shift || 0;
410         my $statuses = shift || [];
411         my $copy_locations = shift || [];
412
413
414         my $offset = abs($page) * $limit;
415         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
416
417         my $o_search = { shortname => $ou };
418         if (!$ou || $ou eq '-') {
419                 $o_search = { parent_ou => undef };
420         }
421
422         my $orgs = $_storage->request(
423                 "open-ils.cstore.direct.actor.org_unit.search",
424                 $o_search,
425                 { flesh         => 100,
426                   flesh_fields  => { aou        => [qw/children/] }
427                 }
428         )->gather(1);
429
430         my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
431
432         $logger->debug("Searching for CNs at orgs [".join(',',@ou_ids)."], based on $ou");
433
434         my @list = ();
435
436     my @cp_filter = ();
437     if (@$statuses || @$copy_locations) {
438         @cp_filter = (
439             '-exists' => {
440                 from  => 'acp',
441                                 where => {
442                     call_number => { '=' => { '+acn' => 'id' } },
443                     deleted     => 'f',
444                     ((@$statuses)       ? ( status   => $statuses)       : ()),
445                                     ((@$copy_locations) ? ( location => $copy_locations) : ())
446                 }
447             }
448         );
449     }
450
451         my $label_sortkey = _label_sortkey_from_label($label, $_storage, \@ou_ids, \@cp_filter);
452
453         if ($page < 0) {
454                 my $before = $_storage->request(
455                         "open-ils.cstore.direct.asset.call_number.search.atomic",
456                         { label_sortkey => { "<" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
457                           owning_lib    => \@ou_ids,
458               deleted => 'f',
459               @cp_filter
460                         },
461                         { flesh         => 1,
462                           flesh_fields  => { acn => [qw/record owning_lib prefix suffix/] },
463                           order_by      => { acn => "oils_text_as_bytea(label_sortkey) desc, oils_text_as_bytea(label) desc, id desc, owning_lib desc" },
464                           limit         => $limit,
465                           offset        => $offset,
466                         }
467                 )->gather(1);
468                 push @list, reverse(@$before);
469         }
470
471         if ($page >= 0) {
472                 my $after = $_storage->request(
473                         "open-ils.cstore.direct.asset.call_number.search.atomic",
474                         { label_sortkey => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label_sortkey] } },
475                           owning_lib    => \@ou_ids,
476               deleted => 'f',
477               @cp_filter
478                         },
479                         { flesh         => 1,
480                           flesh_fields  => { acn => [qw/record owning_lib prefix suffix/] },
481                           order_by      => { acn => "oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib" },
482                           limit         => $limit,
483                           offset        => $offset,
484                         }
485                 )->gather(1);
486                 push @list, @$after;
487         }
488
489         return \@list;
490 }
491 __PACKAGE__->register_method(
492         method    => 'cn_startwith',
493         api_name  => 'open-ils.supercat.call_number.startwith',
494         api_level => 1,
495         argc      => 1,
496         signature =>
497                 { desc     => <<"                 DESC",
498 Returns the XML representation of the requested bibliographic record's holdings
499                   DESC
500                   params   =>
501                         [
502                                 { name => 'label',
503                                   desc => 'The target call number label',
504                                   type => 'string' },
505                                 { name => 'org_unit',
506                                   desc => 'The org unit shortname (or "-" or undef for global) to browse',
507                                   type => 'string' },
508                                 { name => 'page_size',
509                                   desc => 'Count of call numbers to retrieve, default is 9',
510                                   type => 'number' },
511                                 { name => 'page',
512                                   desc => 'The page of call numbers to retrieve, calculated based on page_size.  Can be positive, negative or 0.',
513                                   type => 'number' },
514                                 { name => 'statuses',
515                                   desc => 'Array of statuses to filter copies by, optional and can be undef.',
516                                   type => 'array' },
517                                 { name => 'locations',
518                                   desc => 'Array of copy locations to filter copies by, optional and can be undef.',
519                                   type => 'array' },
520                         ],
521                   'return' =>
522                         { desc => 'Call numbers with owning_lib and record fleshed',
523                           type => 'array' }
524                 }
525 );
526
527
528 sub new_books_by_item {
529         my $self = shift;
530         my $client = shift;
531
532         my $ou = shift;
533         my $page_size = shift || 10;
534         my $page = shift || 1;
535         my $statuses = shift || [];
536         my $copy_locations = shift || [];
537
538     my $offset = $page_size * ($page - 1);
539
540         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
541
542         my @ou_ids;
543         if ($ou && $ou ne '-') {
544                 my $orgs = $_storage->request(
545                         "open-ils.cstore.direct.actor.org_unit.search",
546                         { shortname => $ou },
547                         { flesh         => 100,
548                           flesh_fields  => { aou        => [qw/children/] }
549                         }
550                 )->gather(1);
551                 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
552         }
553
554         $logger->debug("Searching for records with new copies at orgs [".join(',',@ou_ids)."], based on $ou");
555         my $cns = $_storage->request(
556                 "open-ils.cstore.json_query.atomic",
557                 { select        => { acn => ['record'],
558                          acp => [{ aggregate => 1 => transform => max => column => create_date => alias => 'create_date'}]
559                        },
560                   from          => { 'acn' => { 'acp' => { field => call_number => fkey => 'id' } } },
561                   where         =>
562                         { '+acp' =>
563                                 { deleted => 'f',
564                                   ((@ou_ids)          ? ( circ_lib => \@ou_ids)        : ()),
565                                   ((@$statuses)       ? ( status   => $statuses)       : ()),
566                                   ((@$copy_locations) ? ( location => $copy_locations) : ())
567                                 }, 
568                           '+acn' => { record => { '>' => 0 } },
569                         }, 
570                   order_by      => { acp => { create_date => { transform => 'max', direction => 'desc' } } },
571                   limit         => $page_size,
572                   offset        => $offset
573                 }
574         )->gather(1);
575
576         return [ map { $_->{record} } @$cns ];
577 }
578 __PACKAGE__->register_method(
579         method    => 'new_books_by_item',
580         api_name  => 'open-ils.supercat.new_book_list',
581         api_level => 1,
582         argc      => 1,
583         signature =>
584                 { desc     => <<"                 DESC",
585 Returns the XML representation of the requested bibliographic record's holdings
586                   DESC
587                   params   =>
588                         [
589                                 { name => 'org_unit',
590                                   desc => 'The org unit shortname (or "-" or undef for global) to list',
591                                   type => 'string' },
592                                 { name => 'page_size',
593                                   desc => 'Count of records to retrieve, default is 10',
594                                   type => 'number' },
595                                 { name => 'page',
596                                   desc => 'The page of records to retrieve, calculated based on page_size.  Starts at 1.',
597                                   type => 'number' },
598                                 { name => 'statuses',
599                                   desc => 'Array of statuses to filter copies by, optional and can be undef.',
600                                   type => 'array' },
601                                 { name => 'locations',
602                                   desc => 'Array of copy locations to filter copies by, optional and can be undef.',
603                                   type => 'array' },
604                         ],
605                   'return' =>
606                         { desc => 'Record IDs',
607                           type => 'array' }
608                 }
609 );
610
611
612 sub general_browse {
613         my $self = shift;
614         my $client = shift;
615     return tag_sf_browse($self, $client, $self->{tag}, $self->{subfield}, @_);
616 }
617 __PACKAGE__->register_method(
618         method    => 'general_browse',
619         api_name  => 'open-ils.supercat.title.browse',
620         tag       => 'tnf', subfield => 'a',
621         api_level => 1,
622         argc      => 1,
623         signature =>
624                 { desc     => "Returns a list of the requested org-scoped record IDs held",
625                   params   =>
626                         [ { name => 'value', desc => 'The target title', type => 'string' },
627                           { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
628                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
629                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' },
630                           { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
631                           { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
632                   'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
633                 }
634 );
635 __PACKAGE__->register_method(
636         method    => 'general_browse',
637         api_name  => 'open-ils.supercat.author.browse',
638         tag       => [qw/100 110 111/], subfield => 'a',
639         api_level => 1,
640         argc      => 1,
641         signature =>
642                 { desc     => "Returns a list of the requested org-scoped record IDs held",
643                   params   =>
644                         [ { name => 'value', desc => 'The target author', type => 'string' },
645                           { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
646                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
647                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' },
648                           { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
649                           { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
650                   'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
651                 }
652 );
653 __PACKAGE__->register_method(
654         method    => 'general_browse',
655         api_name  => 'open-ils.supercat.subject.browse',
656         tag       => [qw/600 610 611 630 648 650 651 653 655 656 662 690 691 696 697 698 699/], subfield => 'a',
657         api_level => 1,
658         argc      => 1,
659         signature =>
660                 { desc     => "Returns a list of the requested org-scoped record IDs held",
661                   params   =>
662                         [ { name => 'value', desc => 'The target subject', type => 'string' },
663                           { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
664                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
665                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' },
666                           { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
667                           { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
668                   'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
669                 }
670 );
671 __PACKAGE__->register_method(
672         method    => 'general_browse',
673         api_name  => 'open-ils.supercat.topic.browse',
674         tag       => [qw/650 690/], subfield => 'a',
675         api_level => 1,
676         argc      => 1,
677         signature =>
678                 { desc     => "Returns a list of the requested org-scoped record IDs held",
679                   params   =>
680                         [ { name => 'value', desc => 'The target topical subject', type => 'string' },
681                           { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
682                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
683                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' },
684                           { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
685                           { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
686                   'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
687                 }
688 );
689 __PACKAGE__->register_method(
690         method    => 'general_browse',
691         api_name  => 'open-ils.supercat.series.browse',
692         tag       => [qw/440 490 800 810 811 830/], subfield => 'a',
693         api_level => 1,
694         argc      => 1,
695         signature =>
696                 { desc     => "Returns a list of the requested org-scoped record IDs held",
697                   params   =>
698                         [ { name => 'value', desc => 'The target series', type => 'string' },
699                           { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
700                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
701                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' },
702                           { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
703                           { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
704                   'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
705                 }
706 );
707
708
709 sub tag_sf_browse {
710         my $self = shift;
711         my $client = shift;
712
713         my $tag = shift;
714         my $subfield = shift;
715         my $value = shift;
716         my $ou = shift;
717         my $page_size = shift || 9;
718         my $page = shift || 0;
719         my $statuses = shift || [];
720         my $copy_locations = shift || [];
721
722         my ($before_limit,$after_limit) = (0,0);
723         my ($before_offset,$after_offset) = (0,0);
724
725         if (!$page) {
726                 $before_limit = $after_limit = int($page_size / 2);
727                 $after_limit += 1 if ($page_size % 2);
728         } else {
729                 $before_offset = $after_offset = int($page_size / 2);
730                 $before_offset += 1 if ($page_size % 2);
731                 $before_limit = $after_limit = $page_size;
732         }
733
734         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
735
736         my @ou_ids;
737         if ($ou && $ou ne '-') {
738                 my $orgs = $_storage->request(
739                         "open-ils.cstore.direct.actor.org_unit.search",
740                         { shortname => $ou },
741                         { flesh         => 100,
742                           flesh_fields  => { aou        => [qw/children/] }
743                         }
744                 )->gather(1);
745                 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
746         }
747
748         $logger->debug("Searching for records at orgs [".join(',',@ou_ids)."], based on $ou");
749
750         my @list = ();
751
752         if ($page <= 0) {
753                 my $before = $_storage->request(
754                         "open-ils.cstore.json_query.atomic",
755                         { select        => { mfr => [qw/record value/] },
756                           from          => 'mfr',
757                           where         =>
758                                 { '+mfr'        =>
759                                         { tag   => $tag,
760                                           subfield => $subfield,
761                                           value => { '<' => lc($value) }
762                                         },
763                   '-or' => [
764                                 { '-exists'     =>
765                                         { select=> { acp => [ 'id' ] },
766                                           from  => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
767                                               where     =>
768                                                 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
769                                                   '+acp' =>
770                                                                 { deleted => 'f',
771                                                                   ((@ou_ids)          ? ( circ_lib => \@ou_ids)        : ()),
772                                                                   ((@$statuses)       ? ( status   => $statuses)       : ()),
773                                                                   ((@$copy_locations) ? ( location => $copy_locations) : ())
774                                                                 }
775                                                 },
776                                           limit => 1
777                                         }
778                     },
779                     { '-exists' =>
780                                         { select=> { auri => [ 'id' ] },
781                                           from  => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
782                                           where =>
783                                                 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
784                                                   '+auri' => { active => 't' }
785                                                 },
786                                           limit => 1
787                                         }
788                     }
789                   ]
790                                 }, 
791                           order_by      => { mfr => { value => 'desc' } },
792                           limit         => $before_limit,
793                           offset        => abs($page) * $page_size - $before_offset,
794                         }
795                 )->gather(1);
796                 push @list, map { $_->{record} } reverse(@$before);
797         }
798
799         if ($page >= 0) {
800                 my $after = $_storage->request(
801                         "open-ils.cstore.json_query.atomic",
802                         { select        => { mfr => [qw/record value/] },
803                           from          => 'mfr',
804                           where         =>
805                                 { '+mfr'        =>
806                                         { tag   => $tag,
807                                           subfield => $subfield,
808                                           value => { '>=' => lc($value) }
809                                         },
810                                   '-or' => [
811                     { '-exists' =>
812                                         { select=> { acp => [ 'id' ] },
813                                           from  => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
814                                           where =>
815                                                 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
816                                                   '+acp' =>
817                                                                 { deleted => 'f',
818                                                                   ((@ou_ids)          ? ( circ_lib => \@ou_ids)        : ()),
819                                                                   ((@$statuses)       ? ( status   => $statuses)       : ()),
820                                                                   ((@$copy_locations) ? ( location => $copy_locations) : ())
821                                                                 }
822                                                 },
823                                           limit => 1
824                                         }
825                     },
826                     { '-exists' =>
827                                         { select=> { auri => [ 'id' ] },
828                                           from  => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
829                                           where =>
830                                                 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
831                                                   '+auri' => { active => 't' }
832                                                 },
833                                           limit => 1
834                                         },
835                     }
836                   ]
837                                 }, 
838                           order_by      => { mfr => { value => 'asc' } },
839                           limit         => $after_limit,
840                           offset        => abs($page) * $page_size - $after_offset,
841                         }
842                 )->gather(1);
843                 push @list, map { $_->{record} } @$after;
844         }
845
846         return \@list;
847 }
848 __PACKAGE__->register_method(
849         method    => 'tag_sf_browse',
850         api_name  => 'open-ils.supercat.tag.browse',
851         api_level => 1,
852         argc      => 1,
853         signature =>
854                 { desc     => <<"                 DESC",
855 Returns a list of the requested org-scoped record IDs held
856                   DESC
857                   params   =>
858                         [
859                                 { name => 'tag',
860                                   desc => 'The target MARC tag',
861                                   type => 'string' },
862                                 { name => 'subfield',
863                                   desc => 'The target MARC subfield',
864                                   type => 'string' },
865                                 { name => 'value',
866                                   desc => 'The target string',
867                                   type => 'string' },
868                                 { name => 'org_unit',
869                                   desc => 'The org unit shortname (or "-" or undef for global) to browse',
870                                   type => 'string' },
871                                 { name => 'page_size',
872                                   desc => 'Count of call numbers to retrieve, default is 9',
873                                   type => 'number' },
874                                 { name => 'page',
875                                   desc => 'The page of call numbers to retrieve, calculated based on page_size.  Can be positive, negative or 0.',
876                                   type => 'number' },
877                                 { name => 'statuses',
878                                   desc => 'Array of statuses to filter copies by, optional and can be undef.',
879                                   type => 'array' },
880                                 { name => 'locations',
881                                   desc => 'Array of copy locations to filter copies by, optional and can be undef.',
882                                   type => 'array' },
883                         ],
884                   'return' =>
885                         { desc => 'Record IDs that have copies at the relevant org units',
886                           type => 'array' }
887                 }
888 );
889
890 sub grab_authority_browse_axes {
891     my ($self, $client, $full) = @_;
892
893     unless(scalar(keys(%authority_browse_axis_cache))) {
894         my $axes = new_editor->search_authority_browse_axis([
895             { code => { '<>' => undef } },
896             { flesh => 2, flesh_fields => { aba => ['fields'], acsaf => ['bib_fields','sub_entries'] } }
897         ]);
898         $authority_browse_axis_cache{$_->code} = $_ for (@$axes);
899     }
900
901     if ($full) {
902         return [
903             map { $authority_browse_axis_cache{$_} } sort keys %authority_browse_axis_cache
904         ];
905     } else {
906         return [keys %authority_browse_axis_cache];
907     }
908 }
909 __PACKAGE__->register_method(
910         method    => 'grab_authority_browse_axes',
911         api_name  => 'open-ils.supercat.authority.browse_axis_list',
912         api_level => 1,
913         argc      => 1,
914         signature =>
915                 { desc     => "Returns a list of valid authority browse/startswith axes",
916                   params   => [
917               { 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' }
918           ],
919                   'return' => { desc => 'Axis codes or whole axes, see "full" param', type => 'array' }
920                 }
921 );
922
923 sub axis_authority_browse {
924         my $self = shift;
925         my $client = shift;
926     my $axis = shift;
927
928     $axis =~ s/^authority\.//;
929     $axis =~ s/(\.refs)$//;
930     my $refs = $1;
931
932     return undef unless ( grep { /$axis/ } @{ grab_authority_browse_axes() } );
933
934     my @tags;
935     for my $f (@{$authority_browse_axis_cache{$axis}->fields}) {
936         push @tags, $f->tag;
937         if ($refs) {
938             push @tags, $_->tag for @{$f->sub_entries};
939         }
940     }
941
942     return authority_tag_sf_browse($self, $client, \@tags, 'a', @_); # XXX TODO figure out something more correct for the subfield param
943 }
944 __PACKAGE__->register_method(
945         method    => 'axis_authority_browse',
946         api_name  => 'open-ils.supercat.authority.browse.by_axis',
947         api_level => 1,
948         argc      => 2,
949         signature =>
950                 { desc     => "Returns a list of the requested authority record IDs held",
951                   params   =>
952                         [ { name => 'axis', desc => 'The target axis', type => 'string' },
953                           { name => 'value', desc => 'The target value', type => 'string' },
954                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
955                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
956                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
957                 }
958 );
959
960 =pod
961
962 sub general_authority_browse {
963         my $self = shift;
964         my $client = shift;
965     return authority_tag_sf_browse($self, $client, $self->{tag}, $self->{subfield}, @_);
966 }
967 __PACKAGE__->register_method(
968         method    => 'general_authority_browse',
969         api_name  => 'open-ils.supercat.authority.title.browse',
970         tag       => ['130'], subfield => 'a',
971         api_level => 1,
972         argc      => 1,
973         signature =>
974                 { desc     => "Returns a list of the requested authority record IDs held",
975                   params   =>
976                         [ { name => 'value', desc => 'The target title', type => 'string' },
977                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
978                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
979                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
980                 }
981 );
982 __PACKAGE__->register_method(
983         method    => 'general_authority_browse',
984         api_name  => 'open-ils.supercat.authority.author.browse',
985         tag       => [qw/100 110 111/], subfield => 'a',
986         api_level => 1,
987         argc      => 1,
988         signature =>
989                 { desc     => "Returns a list of the requested authority record IDs held",
990                   params   =>
991                         [ { name => 'value', desc => 'The target author', type => 'string' },
992                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
993                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
994                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
995                 }
996 );
997 __PACKAGE__->register_method(
998         method    => 'general_authority_browse',
999         api_name  => 'open-ils.supercat.authority.subject.browse',
1000         tag       => [qw/148 150 151 155/], subfield => 'a',
1001         api_level => 1,
1002         argc      => 1,
1003         signature =>
1004                 { desc     => "Returns a list of the requested authority record IDs held",
1005                   params   =>
1006                         [ { name => 'value', desc => 'The target subject', type => 'string' },
1007                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1008                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
1009                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1010                 }
1011 );
1012 __PACKAGE__->register_method(
1013         method    => 'general_authority_browse',
1014         api_name  => 'open-ils.supercat.authority.topic.browse',
1015         tag       => ['150'], subfield => 'a',
1016         api_level => 1,
1017         argc      => 1,
1018         signature =>
1019                 { desc     => "Returns a list of the requested authority record IDs held",
1020                   params   =>
1021                         [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1022                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1023                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
1024                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1025                 }
1026 );
1027 __PACKAGE__->register_method(
1028         method    => 'general_authority_browse',
1029         api_name  => 'open-ils.supercat.authority.title.refs.browse',
1030         tag       => ['130'], subfield => 'a',
1031         api_level => 1,
1032         argc      => 1,
1033         signature =>
1034                 { desc     => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1035                   params   =>
1036                         [ { name => 'value', desc => 'The target title', type => 'string' },
1037                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1038                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
1039                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1040                 }
1041 );
1042 __PACKAGE__->register_method(
1043         method    => 'general_authority_browse',
1044         api_name  => 'open-ils.supercat.authority.author.refs.browse',
1045         tag       => [qw/100 110 111/], subfield => 'a',
1046         api_level => 1,
1047         argc      => 1,
1048         signature =>
1049                 { desc     => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1050                   params   =>
1051                         [ { name => 'value', desc => 'The target author', type => 'string' },
1052                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1053                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
1054                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1055                 }
1056 );
1057 __PACKAGE__->register_method(
1058         method    => 'general_authority_browse',
1059         api_name  => 'open-ils.supercat.authority.subject.refs.browse',
1060         tag       => [qw/148 150 151 155/], subfield => 'a',
1061         api_level => 1,
1062         argc      => 1,
1063         signature =>
1064                 { desc     => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1065                   params   =>
1066                         [ { name => 'value', desc => 'The target subject', type => 'string' },
1067                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1068                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
1069                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1070                 }
1071 );
1072 __PACKAGE__->register_method(
1073         method    => 'general_authority_browse',
1074         api_name  => 'open-ils.supercat.authority.topic.refs.browse',
1075         tag       => ['150'], subfield => 'a',
1076         api_level => 1,
1077         argc      => 1,
1078         signature =>
1079                 { desc     => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1080                   params   =>
1081                         [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1082                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1083                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
1084                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1085                 }
1086 );
1087
1088 =cut
1089
1090 sub authority_tag_sf_browse {
1091     my $self = shift;
1092     my $client = shift;
1093
1094     my $tag = shift;
1095     my $subfield = shift;
1096     my $value = shift;
1097     my $page_size = shift || 9;
1098     my $page = shift || 0;
1099
1100     # Match authority.full_rec normalization
1101     $value = naco_normalize($value, $subfield);
1102
1103     my ($before_limit,$after_limit) = (0,0);
1104     my ($before_offset,$after_offset) = (0,0);
1105
1106     if (!$page) {
1107         $before_limit = $after_limit = int($page_size / 2);
1108         $after_limit += 1 if ($page_size % 2);
1109     } else {
1110         $before_offset = $after_offset = int($page_size / 2);
1111         $before_offset += 1 if ($page_size % 2);
1112         $before_limit = $after_limit = $page_size;
1113     }
1114
1115     my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1116
1117     # .refs variant includes 4xx and 5xx variants for see / see also
1118     my @ref_tags = ();
1119     foreach my $tagname (@$tag) {
1120         push(@ref_tags, $tagname);
1121         if ($self->api_name =~ /\.refs\./) {
1122             push(@ref_tags, '4' . substr($tagname, 1, 2));
1123             push(@ref_tags, '5' . substr($tagname, 1, 2));
1124         }
1125     }
1126     my @list = ();
1127
1128     if ($page <= 0) {
1129         my $before = $_storage->request(
1130             "open-ils.cstore.json_query.atomic",
1131             { select    => { afr => [qw/record value/] },
1132               from      => 'afr',
1133               where     => { tag => \@ref_tags, subfield => $subfield, value => { '<' => $value } },
1134               order_by  => { afr => { value => 'desc' } },
1135               limit     => $before_limit,
1136               offset    => abs($page) * $page_size - $before_offset,
1137             }
1138         )->gather(1);
1139         push @list, map { $_->{record} } reverse(@$before);
1140     }
1141
1142     if ($page >= 0) {
1143         my $after = $_storage->request(
1144             "open-ils.cstore.json_query.atomic",
1145             { select    => { afr => [qw/record value/] },
1146               from      => 'afr',
1147               where     => { tag => \@ref_tags, subfield => $subfield, value => { '>=' => $value } },
1148               order_by  => { afr => { value => 'asc' } },
1149               limit     => $after_limit,
1150               offset    => abs($page) * $page_size - $after_offset,
1151             }
1152         )->gather(1);
1153         push @list, map { $_->{record} } @$after;
1154     }
1155
1156     # If we're not pulling in see/see also references, just return the raw list
1157     if ($self->api_name !~ /\.refs\./) {
1158         return \@list;
1159     } 
1160
1161     # Remove dupe record IDs that turn up due to 4xx and 5xx matches
1162     my @retlist = ();
1163     my %seen;
1164     foreach my $record (@list) {
1165         next if exists $seen{$record};
1166         push @retlist, int($record);
1167         $seen{$record} = 1;
1168     }
1169
1170     return \@retlist;
1171 }
1172 __PACKAGE__->register_method(
1173         method    => 'authority_tag_sf_browse',
1174         api_name  => 'open-ils.supercat.authority.tag.browse',
1175         api_level => 1,
1176         argc      => 1,
1177         signature =>
1178                 { desc     => <<"                 DESC",
1179 Returns a list of the requested authority record IDs held
1180                   DESC
1181                   params   =>
1182                         [
1183                                 { name => 'tag',
1184                                   desc => 'The target Authority MARC tag',
1185                                   type => 'string' },
1186                                 { name => 'subfield',
1187                                   desc => 'The target Authority MARC subfield',
1188                                   type => 'string' },
1189                                 { name => 'value',
1190                                   desc => 'The target string',
1191                                   type => 'string' },
1192                                 { name => 'page_size',
1193                                   desc => 'Count of call numbers to retrieve, default is 9',
1194                                   type => 'number' },
1195                                 { name => 'page',
1196                                   desc => 'The page of call numbers to retrieve, calculated based on page_size.  Can be positive, negative or 0.',
1197                                   type => 'number' },
1198                         ],
1199                   'return' =>
1200                         { desc => 'Authority Record IDs that are near the target string',
1201                           type => 'array' }
1202                 }
1203 );
1204
1205 sub general_startwith {
1206         my $self = shift;
1207         my $client = shift;
1208     return tag_sf_startwith($self, $client, $self->{tag}, $self->{subfield}, @_);
1209 }
1210 __PACKAGE__->register_method(
1211         method    => 'general_startwith',
1212         api_name  => 'open-ils.supercat.title.startwith',
1213         tag       => 'tnf', subfield => 'a',
1214         api_level => 1,
1215         argc      => 1,
1216         signature =>
1217                 { desc     => "Returns a list of the requested org-scoped record IDs held",
1218                   params   =>
1219                         [ { name => 'value', desc => 'The target title', type => 'string' },
1220                           { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1221                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1222                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' },
1223                           { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1224                           { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1225                   'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1226                 }
1227 );
1228 __PACKAGE__->register_method(
1229         method    => 'general_startwith',
1230         api_name  => 'open-ils.supercat.author.startwith',
1231         tag       => [qw/100 110 111/], subfield => 'a',
1232         api_level => 1,
1233         argc      => 1,
1234         signature =>
1235                 { desc     => "Returns a list of the requested org-scoped record IDs held",
1236                   params   =>
1237                         [ { name => 'value', desc => 'The target author', type => 'string' },
1238                           { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1239                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1240                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' },
1241                           { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1242                           { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1243                   'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1244                 }
1245 );
1246 __PACKAGE__->register_method(
1247         method    => 'general_startwith',
1248         api_name  => 'open-ils.supercat.subject.startwith',
1249         tag       => [qw/600 610 611 630 648 650 651 653 655 656 662 690 691 696 697 698 699/], subfield => 'a',
1250         api_level => 1,
1251         argc      => 1,
1252         signature =>
1253                 { desc     => "Returns a list of the requested org-scoped record IDs held",
1254                   params   =>
1255                         [ { name => 'value', desc => 'The target subject', type => 'string' },
1256                           { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1257                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1258                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' },
1259                           { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1260                           { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1261                   'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1262                 }
1263 );
1264 __PACKAGE__->register_method(
1265         method    => 'general_startwith',
1266         api_name  => 'open-ils.supercat.topic.startwith',
1267         tag       => [qw/650 690/], subfield => 'a',
1268         api_level => 1,
1269         argc      => 1,
1270         signature =>
1271                 { desc     => "Returns a list of the requested org-scoped record IDs held",
1272                   params   =>
1273                         [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1274                           { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1275                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1276                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' },
1277                           { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1278                           { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1279                   'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1280                 }
1281 );
1282 __PACKAGE__->register_method(
1283         method    => 'general_startwith',
1284         api_name  => 'open-ils.supercat.series.startwith',
1285         tag       => [qw/440 490 800 810 811 830/], subfield => 'a',
1286         api_level => 1,
1287         argc      => 1,
1288         signature =>
1289                 { desc     => "Returns a list of the requested org-scoped record IDs held",
1290                   params   =>
1291                         [ { name => 'value', desc => 'The target series', type => 'string' },
1292                           { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1293                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1294                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' },
1295                           { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1296                           { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1297                   'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1298                 }
1299 );
1300
1301
1302 sub tag_sf_startwith {
1303         my $self = shift;
1304         my $client = shift;
1305
1306         my $tag = shift;
1307         my $subfield = shift;
1308         my $value = shift;
1309         my $ou = shift;
1310         my $limit = shift || 10;
1311         my $page = shift || 0;
1312         my $statuses = shift || [];
1313         my $copy_locations = shift || [];
1314
1315         my $offset = $limit * abs($page);
1316         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1317
1318         my @ou_ids;
1319         if ($ou && $ou ne '-') {
1320                 my $orgs = $_storage->request(
1321                         "open-ils.cstore.direct.actor.org_unit.search",
1322                         { shortname => $ou },
1323                         { flesh         => 100,
1324                           flesh_fields  => { aou        => [qw/children/] }
1325                         }
1326                 )->gather(1);
1327                 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
1328         }
1329
1330         $logger->debug("Searching for records at orgs [".join(',',@ou_ids)."], based on $ou");
1331
1332         my @list = ();
1333
1334         if ($page < 0) {
1335                 my $before = $_storage->request(
1336                         "open-ils.cstore.json_query.atomic",
1337                         { select        => { mfr => [qw/record value/] },
1338                           from          => 'mfr',
1339                           where         =>
1340                                 { '+mfr'        =>
1341                                         { tag   => $tag,
1342                                           subfield => $subfield,
1343                                           value => { '<' => lc($value) }
1344                                         },
1345                   '-or' => [
1346                                 { '-exists'     =>
1347                                         { select=> { acp => [ 'id' ] },
1348                                           from  => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
1349                                               where     =>
1350                                                 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
1351                                                   '+acp' =>
1352                                                                 { deleted => 'f',
1353                                                                   ((@ou_ids)          ? ( circ_lib => \@ou_ids)        : ()),
1354                                                                   ((@$statuses)       ? ( status   => $statuses)       : ()),
1355                                                                   ((@$copy_locations) ? ( location => $copy_locations) : ())
1356                                                                 }
1357                                                 },
1358                                           limit => 1
1359                                         }
1360                     },
1361                     { '-exists' =>
1362                                         { select=> { auri => [ 'id' ] },
1363                                           from  => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
1364                                           where =>
1365                                                 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
1366                                                   '+auri' => { active => 't' }
1367                                                 },
1368                                           limit => 1
1369                                         }
1370                     }
1371                   ]
1372                                 }, 
1373                           order_by      => { mfr => { value => 'desc' } },
1374                           limit         => $limit,
1375                           offset        => $offset
1376                         }
1377                 )->gather(1);
1378                 push @list, map { $_->{record} } reverse(@$before);
1379         }
1380
1381         if ($page >= 0) {
1382                 my $after = $_storage->request(
1383                         "open-ils.cstore.json_query.atomic",
1384                         { select        => { mfr => [qw/record value/] },
1385                           from          => 'mfr',
1386                           where         =>
1387                                 { '+mfr'        =>
1388                                         { tag   => $tag,
1389                                           subfield => $subfield,
1390                                           value => { '>=' => lc($value) }
1391                                         },
1392                                   '-or' => [
1393                     { '-exists' =>
1394                                         { select=> { acp => [ 'id' ] },
1395                                           from  => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
1396                                           where =>
1397                                                 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
1398                                                   '+acp' =>
1399                                                                 { deleted => 'f',
1400                                                                   ((@ou_ids)          ? ( circ_lib => \@ou_ids)        : ()),
1401                                                                   ((@$statuses)       ? ( status   => $statuses)       : ()),
1402                                                                   ((@$copy_locations) ? ( location => $copy_locations) : ())
1403                                                                 }
1404                                                 },
1405                                           limit => 1
1406                                         }
1407                     },
1408                     { '-exists' =>
1409                                         { select=> { auri => [ 'id' ] },
1410                                           from  => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
1411                                           where =>
1412                                                 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
1413                                                   '+auri' => { active => 't' }
1414                                                 },
1415                                           limit => 1
1416                                         },
1417                     }
1418                   ]
1419                                 }, 
1420                           order_by      => { mfr => { value => 'asc' } },
1421                           limit         => $limit,
1422                           offset        => $offset
1423                         }
1424                 )->gather(1);
1425                 push @list, map { $_->{record} } @$after;
1426         }
1427
1428         return \@list;
1429 }
1430 __PACKAGE__->register_method(
1431         method    => 'tag_sf_startwith',
1432         api_name  => 'open-ils.supercat.tag.startwith',
1433         api_level => 1,
1434         argc      => 1,
1435         signature =>
1436                 { desc     => <<"                 DESC",
1437 Returns a list of the requested org-scoped record IDs held
1438                   DESC
1439                   params   =>
1440                         [
1441                                 { name => 'tag',
1442                                   desc => 'The target MARC tag',
1443                                   type => 'string' },
1444                                 { name => 'subfield',
1445                                   desc => 'The target MARC subfield',
1446                                   type => 'string' },
1447                                 { name => 'value',
1448                                   desc => 'The target string',
1449                                   type => 'string' },
1450                                 { name => 'org_unit',
1451                                   desc => 'The org unit shortname (or "-" or undef for global) to browse',
1452                                   type => 'string' },
1453                                 { name => 'page_size',
1454                                   desc => 'Count of call numbers to retrieve, default is 9',
1455                                   type => 'number' },
1456                                 { name => 'page',
1457                                   desc => 'The page of call numbers to retrieve, calculated based on page_size.  Can be positive, negative or 0.',
1458                                   type => 'number' },
1459                                 { name => 'statuses',
1460                                   desc => 'Array of statuses to filter copies by, optional and can be undef.',
1461                                   type => 'array' },
1462                                 { name => 'locations',
1463                                   desc => 'Array of copy locations to filter copies by, optional and can be undef.',
1464                                   type => 'array' },
1465                         ],
1466                   'return' =>
1467                         { desc => 'Record IDs that have copies at the relevant org units',
1468                           type => 'array' }
1469                 }
1470 );
1471
1472 sub axis_authority_startwith {
1473         my $self = shift;
1474         my $client = shift;
1475     my $axis = shift;
1476
1477     $axis =~ s/^authority\.//;
1478     $axis =~ s/(\.refs)$//;
1479     my $refs = $1;
1480
1481     return undef unless ( grep { /$axis/ } @{ grab_authority_browse_axes() } );
1482
1483     my @tags;
1484     for my $f (@{$authority_browse_axis_cache{$axis}->fields}) {
1485         push @tags, $f->tag;
1486         if ($refs) {
1487             push @tags, $_->tag for @{$f->sub_entries};
1488         }
1489     }
1490
1491     return authority_tag_sf_startwith($self, $client, \@tags, 'a', @_); # XXX TODO figure out something more correct for the subfield param
1492 }
1493 __PACKAGE__->register_method(
1494         method    => 'axis_authority_startwith',
1495         api_name  => 'open-ils.supercat.authority.startwith.by_axis',
1496         api_level => 1,
1497         argc      => 2,
1498         signature =>
1499                 { desc     => "Returns a list of the requested authority record IDs held",
1500                   params   =>
1501                         [ { name => 'axis', desc => 'The target axis', type => 'string' },
1502                           { name => 'value', desc => 'The target value', type => 'string' },
1503                           { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1504                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
1505                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1506                 }
1507 );
1508
1509 =pod
1510
1511 sub general_authority_startwith {
1512         my $self = shift;
1513         my $client = shift;
1514     return authority_tag_sf_startwith($self, $client, $self->{tag}, $self->{subfield}, @_);
1515 }
1516 __PACKAGE__->register_method(
1517         method    => 'general_authority_startwith',
1518         api_name  => 'open-ils.supercat.authority.title.startwith',
1519         tag       => ['130'], subfield => 'a',
1520         api_level => 1,
1521         argc      => 1,
1522         signature =>
1523                 { desc     => "Returns a list of the requested authority record IDs held",
1524                   params   =>
1525                         [ { name => 'value', desc => 'The target title', type => 'string' },
1526                           { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1527                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
1528                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1529                 }
1530 );
1531 __PACKAGE__->register_method(
1532         method    => 'general_authority_startwith',
1533         api_name  => 'open-ils.supercat.authority.author.startwith',
1534         tag       => [qw/100 110 111/], subfield => 'a',
1535         api_level => 1,
1536         argc      => 1,
1537         signature =>
1538                 { desc     => "Returns a list of the requested authority record IDs held",
1539                   params   =>
1540                         [ { name => 'value', desc => 'The target author', type => 'string' },
1541                           { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1542                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
1543                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1544                 }
1545 );
1546 __PACKAGE__->register_method(
1547         method    => 'general_authority_startwith',
1548         api_name  => 'open-ils.supercat.authority.subject.startwith',
1549         tag       => [qw/148 150 151 155/], subfield => 'a',
1550         api_level => 1,
1551         argc      => 1,
1552         signature =>
1553                 { desc     => "Returns a list of the requested authority record IDs held",
1554                   params   =>
1555                         [ { name => 'value', desc => 'The target subject', type => 'string' },
1556                           { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1557                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
1558                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1559                 }
1560 );
1561 __PACKAGE__->register_method(
1562         method    => 'general_authority_startwith',
1563         api_name  => 'open-ils.supercat.authority.topic.startwith',
1564         tag       => ['150'], subfield => 'a',
1565         api_level => 1,
1566         argc      => 1,
1567         signature =>
1568                 { desc     => "Returns a list of the requested authority record IDs held",
1569                   params   =>
1570                         [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1571                           { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1572                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
1573                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1574                 }
1575 );
1576 __PACKAGE__->register_method(
1577         method    => 'general_authority_startwith',
1578         api_name  => 'open-ils.supercat.authority.title.refs.startwith',
1579         tag       => ['130'], subfield => 'a',
1580         api_level => 1,
1581         argc      => 1,
1582         signature =>
1583                 { desc     => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1584                   params   =>
1585                         [ { name => 'value', desc => 'The target title', type => 'string' },
1586                           { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1587                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
1588                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1589                 }
1590 );
1591 __PACKAGE__->register_method(
1592         method    => 'general_authority_startwith',
1593         api_name  => 'open-ils.supercat.authority.author.refs.startwith',
1594         tag       => [qw/100 110 111/], subfield => 'a',
1595         api_level => 1,
1596         argc      => 1,
1597         signature =>
1598                 { desc     => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1599                   params   =>
1600                         [ { name => 'value', desc => 'The target author', type => 'string' },
1601                           { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1602                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
1603                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1604                 }
1605 );
1606 __PACKAGE__->register_method(
1607         method    => 'general_authority_startwith',
1608         api_name  => 'open-ils.supercat.authority.subject.refs.startwith',
1609         tag       => [qw/148 150 151 155/], subfield => 'a',
1610         api_level => 1,
1611         argc      => 1,
1612         signature =>
1613                 { desc     => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1614                   params   =>
1615                         [ { name => 'value', desc => 'The target subject', type => 'string' },
1616                           { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1617                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
1618                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1619                 }
1620 );
1621 __PACKAGE__->register_method(
1622         method    => 'general_authority_startwith',
1623         api_name  => 'open-ils.supercat.authority.topic.refs.startwith',
1624         tag       => ['150'], subfield => 'a',
1625         api_level => 1,
1626         argc      => 1,
1627         signature =>
1628                 { desc     => "Returns a list of the requested authority record IDs held, including see (4xx) and see also (5xx) references",
1629                   params   =>
1630                         [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1631                           { name => 'page_size', desc => 'Count of records to retrieve, default is 10', type => 'number' },
1632                           { name => 'page', desc => 'The page of records retrieved, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
1633                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1634                 }
1635 );
1636
1637 =cut
1638
1639 sub authority_tag_sf_startwith {
1640     my $self = shift;
1641     my $client = shift;
1642
1643     my $tag = shift;
1644     my $subfield = shift;
1645
1646     my $value = shift;
1647     my $limit = shift || 10;
1648     my $page = shift || 0;
1649
1650     # Match authority.full_rec normalization
1651     $value = naco_normalize($value, $subfield);
1652
1653     my $ref_limit = $limit;
1654     my $offset = $limit * abs($page);
1655     my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1656
1657     my @ref_tags = ();
1658     # .refs variant includes 4xx and 5xx variants for see / see also
1659     foreach my $tagname (@$tag) {
1660         push(@ref_tags, $tagname);
1661         if ($self->api_name =~ /\.refs\./) {
1662             push(@ref_tags, '4' . substr($tagname, 1, 2));
1663             push(@ref_tags, '5' . substr($tagname, 1, 2));
1664         }
1665     }
1666
1667     my @list = ();
1668
1669     if ($page < 0) {
1670         # Don't skip the first actual page of results in descending order
1671         $offset = $offset - $limit;
1672
1673         my $before = $_storage->request(
1674             "open-ils.cstore.json_query.atomic",
1675             { select    => { afr => [qw/record value/] },
1676               from      => 'afr',
1677               where     => { tag => \@ref_tags, subfield => $subfield, value => { '<' => $value } },
1678               order_by  => { afr => { value => 'desc' } },
1679               limit     => $ref_limit,
1680               offset    => $offset,
1681             }
1682         )->gather(1);
1683         push @list, map { $_->{record} } reverse(@$before);
1684     }
1685
1686     if ($page >= 0) {
1687         my $after = $_storage->request(
1688             "open-ils.cstore.json_query.atomic",
1689             { select    => { afr => [qw/record value/] },
1690               from      => 'afr',
1691               where     => { tag => \@ref_tags, subfield => $subfield, value => { '>=' => $value } },
1692               order_by  => { afr => { value => 'asc' } },
1693               limit     => $ref_limit,
1694               offset    => $offset,
1695             }
1696         )->gather(1);
1697         push @list, map { $_->{record} } @$after;
1698     }
1699
1700     # If we're not pulling in see/see also references, just return the raw list
1701     if ($self->api_name !~ /\.refs\./) {
1702         return \@list;
1703     }
1704
1705     # Remove dupe record IDs that turn up due to 4xx and 5xx matches
1706     my @retlist = ();
1707     my %seen;
1708     foreach my $record (@list) {
1709         next if exists $seen{$record};
1710         push @retlist, int($record);
1711         $seen{$record} = 1;
1712     }
1713
1714     return \@retlist;
1715 }
1716 __PACKAGE__->register_method(
1717         method    => 'authority_tag_sf_startwith',
1718         api_name  => 'open-ils.supercat.authority.tag.startwith',
1719         api_level => 1,
1720         argc      => 1,
1721         signature =>
1722                 { desc     => <<"                 DESC",
1723 Returns a list of the requested authority record IDs held
1724                   DESC
1725                   params   =>
1726                         [
1727                                 { name => 'tag',
1728                                   desc => 'The target Authority MARC tag',
1729                                   type => 'string' },
1730                                 { name => 'subfield',
1731                                   desc => 'The target Authority MARC subfield',
1732                                   type => 'string' },
1733                                 { name => 'value',
1734                                   desc => 'The target string',
1735                                   type => 'string' },
1736                                 { name => 'page_size',
1737                                   desc => 'Count of call numbers to retrieve, default is 10',
1738                                   type => 'number' },
1739                                 { name => 'page',
1740                                   desc => 'The page of call numbers to retrieve, calculated based on page_size.  Can be positive, negative or 0.',
1741                                   type => 'number' },
1742                         ],
1743                   'return' =>
1744                         { desc => 'Authority Record IDs that are near the target string',
1745                           type => 'array' }
1746                 }
1747 );
1748
1749
1750 sub holding_data_formats {
1751     return [{
1752         marcxml => {
1753             namespace_uri         => 'http://www.loc.gov/MARC21/slim',
1754                         docs              => 'http://www.loc.gov/marcxml/',
1755                         schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
1756                 }
1757         }];
1758 }
1759 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.acn.formats', api_level => 1 );
1760 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.acp.formats', api_level => 1 );
1761 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.auri.formats', api_level => 1 );
1762
1763
1764 __PACKAGE__->register_method(
1765         method    => 'retrieve_uri',
1766         api_name  => 'open-ils.supercat.auri.marcxml.retrieve',
1767         api_level => 1,
1768         argc      => 1,
1769         signature =>
1770                 { desc     => <<"                 DESC",
1771 Returns a fleshed call number object
1772                   DESC
1773                   params   =>
1774                         [
1775                                 { name => 'uri_id',
1776                                   desc => 'An OpenILS asset::uri id',
1777                                   type => 'number' },
1778                         ],
1779                   'return' =>
1780                         { desc => 'fleshed uri',
1781                           type => 'object' }
1782                 }
1783 );
1784 sub retrieve_uri {
1785         my $self = shift;
1786         my $client = shift;
1787         my $cpid = shift;
1788         my $args = shift || {};
1789
1790     return OpenILS::Application::SuperCat::unAPI
1791         ->new(OpenSRF::AppSession
1792             ->create( 'open-ils.cstore' )
1793             ->request(
1794                 "open-ils.cstore.direct.asset.uri.retrieve",
1795                     $cpid,
1796                     { flesh             => 10,
1797                           flesh_fields  => {
1798                                                 auri    => [qw/call_number_maps/],
1799                                                 auricnm => [qw/call_number/],
1800                                                 acn         => [qw/owning_lib record prefix suffix/],
1801                                 }
1802                     })
1803             ->gather(1))
1804         ->as_xml($args);
1805 }
1806
1807 __PACKAGE__->register_method(
1808         method    => 'retrieve_copy',
1809         api_name  => 'open-ils.supercat.acp.marcxml.retrieve',
1810         api_level => 1,
1811         argc      => 1,
1812         signature =>
1813                 { desc     => <<"                 DESC",
1814 Returns a fleshed call number object
1815                   DESC
1816                   params   =>
1817                         [
1818                                 { name => 'cn_id',
1819                                   desc => 'An OpenILS asset::copy id',
1820                                   type => 'number' },
1821                         ],
1822                   'return' =>
1823                         { desc => 'fleshed copy',
1824                           type => 'object' }
1825                 }
1826 );
1827 sub retrieve_copy {
1828         my $self = shift;
1829         my $client = shift;
1830         my $cpid = shift;
1831         my $args = shift || {};
1832
1833     return OpenILS::Application::SuperCat::unAPI
1834         ->new(OpenSRF::AppSession
1835             ->create( 'open-ils.cstore' )
1836             ->request(
1837                 "open-ils.cstore.direct.asset.copy.retrieve",
1838                     $cpid,
1839                     { flesh             => 2,
1840                           flesh_fields  => {
1841                                                 acn     => [qw/owning_lib record prefix suffix/],
1842                                                 acp     => [qw/call_number location status circ_lib stat_cat_entries notes parts/],
1843                                 }
1844                     })
1845             ->gather(1))
1846         ->as_xml($args);
1847 }
1848
1849 __PACKAGE__->register_method(
1850         method    => 'retrieve_callnumber',
1851         api_name  => 'open-ils.supercat.acn.marcxml.retrieve',
1852         api_level => 1,
1853         argc      => 1,
1854         stream    => 1,
1855         signature =>
1856                 { desc     => <<"                 DESC",
1857 Returns a fleshed call number object
1858                   DESC
1859                   params   =>
1860                         [
1861                                 { name => 'cn_id',
1862                                   desc => 'An OpenILS asset::call_number id',
1863                                   type => 'number' },
1864                         ],
1865                   'return' =>
1866                         { desc => 'call number with copies',
1867                           type => 'object' }
1868                 }
1869 );
1870 sub retrieve_callnumber {
1871         my $self = shift;
1872         my $client = shift;
1873         my $cnid = shift;
1874         my $args = shift || {};
1875
1876     return OpenILS::Application::SuperCat::unAPI
1877         ->new(OpenSRF::AppSession
1878             ->create( 'open-ils.cstore' )
1879             ->request(
1880                 "open-ils.cstore.direct.asset.call_number.retrieve",
1881                     $cnid,
1882                     { flesh             => 5,
1883                           flesh_fields  => {
1884                                                 acn     => [qw/owning_lib record copies uri_maps prefix suffix/],
1885                                                 auricnm => [qw/uri/],
1886                                                 acp     => [qw/location status circ_lib stat_cat_entries notes parts/],
1887                                 }
1888                     })
1889             ->gather(1))
1890         ->as_xml($args);
1891
1892 }
1893
1894 __PACKAGE__->register_method(
1895         method    => 'basic_record_holdings',
1896         api_name  => 'open-ils.supercat.record.basic_holdings.retrieve',
1897         api_level => 1,
1898         argc      => 1,
1899         stream    => 1,
1900         signature =>
1901                 { desc     => <<"                 DESC",
1902 Returns a basic hash representation of the requested bibliographic record's holdings
1903                   DESC
1904                   params   =>
1905                         [
1906                                 { name => 'bibId',
1907                                   desc => 'An OpenILS biblio::record_entry id',
1908                                   type => 'number' },
1909                         ],
1910                   'return' =>
1911                         { desc => 'Hash of bib record holdings hierarchy (call numbers and copies)',
1912                           type => 'string' }
1913                 }
1914 );
1915 sub basic_record_holdings {
1916         my $self = shift;
1917         my $client = shift;
1918         my $bib = shift;
1919         my $ou = shift;
1920
1921         #  holdings hold an array of call numbers, which hold an array of copies
1922         #  holdings => [ label: { library, [ copies: { barcode, location, status, circ_lib } ] } ]
1923         my %holdings;
1924
1925         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1926
1927         my $tree = $_storage->request(
1928                 "open-ils.cstore.direct.biblio.record_entry.retrieve",
1929                 $bib,
1930                 { flesh         => 5,
1931                   flesh_fields  => {
1932                                         bre     => [qw/call_numbers/],
1933                                         acn     => [qw/copies owning_lib prefix suffix/],
1934                                         acp     => [qw/location status circ_lib parts/],
1935                                 }
1936                 }
1937         )->gather(1);
1938
1939         my $o_search = { shortname => uc($ou) };
1940         if (!$ou || $ou eq '-') {
1941                 $o_search = { parent_ou => undef };
1942         }
1943
1944         my $orgs = $_storage->request(
1945                 "open-ils.cstore.direct.actor.org_unit.search",
1946                 $o_search,
1947                 { flesh         => 100,
1948                   flesh_fields  => { aou        => [qw/children/] }
1949                 }
1950         )->gather(1);
1951
1952         my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
1953
1954         $logger->debug("Searching for holdings at orgs [".join(',',@ou_ids)."], based on $ou");
1955
1956         for my $cn (@{$tree->call_numbers}) {
1957         next unless ( $cn->deleted eq 'f' || $cn->deleted == 0 );
1958
1959                 my $found = 0;
1960                 for my $c (@{$cn->copies}) {
1961                         next unless grep {$c->circ_lib->id == $_} @ou_ids;
1962                         next unless ( $c->deleted eq 'f' || $c->deleted == 0 );
1963                         $found = 1;
1964                         last;
1965                 }
1966                 next unless $found;
1967
1968                 $holdings{$cn->label}{'owning_lib'} = $cn->owning_lib->shortname;
1969
1970                 for my $cp (@{$cn->copies}) {
1971
1972                         next unless grep { $cp->circ_lib->id == $_ } @ou_ids;
1973                         next unless ( $cp->deleted eq 'f' || $cp->deleted == 0 );
1974
1975                         push @{$holdings{$cn->label}{'copies'}}, {
1976                 barcode => $cp->barcode,
1977                 status => $cp->status->name,
1978                 location => $cp->location->name,
1979                 circlib => $cp->circ_lib->shortname
1980             };
1981
1982                 }
1983         }
1984
1985         return \%holdings;
1986 }
1987
1988 #__PACKAGE__->register_method(
1989 #       method    => 'new_record_holdings',
1990 #       api_name  => 'open-ils.supercat.record.holdings_xml.retrieve',
1991 #       api_level => 1,
1992 #       argc      => 1,
1993 #       stream    => 1,
1994 #       signature =>
1995 #               { desc     => <<"                 DESC",
1996 #Returns the XML representation of the requested bibliographic record's holdings
1997 #                 DESC
1998 #                 params   =>
1999 #                       [
2000 #                               { name => 'bibId',
2001 #                                 desc => 'An OpenILS biblio::record_entry id',
2002 #                                 type => 'number' },
2003 #                       ],
2004 #                 'return' =>
2005 #                       { desc => 'Stream of bib record holdings hierarchy in XML',
2006 #                         type => 'string' }
2007 #               }
2008 #);
2009 #
2010
2011 sub new_record_holdings {
2012         my $self = shift;
2013         my $client = shift;
2014         my $bib = shift;
2015         my $ou = shift;
2016         my $depth = shift;
2017         my $flesh = shift;
2018         my $paging = shift;
2019
2020     $paging = [-1,0] if (!$paging or !ref($paging) or @$paging == 0);
2021     my $limit = $$paging[0];
2022     my $offset = $$paging[1] || 0;
2023
2024         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2025         my $_search = OpenSRF::AppSession->create( 'open-ils.search' );
2026
2027         my $o_search = { shortname => uc($ou) };
2028         if (!$ou || $ou eq '-') {
2029                 $o_search = { parent_ou => undef };
2030         }
2031
2032     my $one_org = $_storage->request(
2033         "open-ils.cstore.direct.actor.org_unit.search",
2034         $o_search
2035     )->gather(1);
2036
2037     my $count_req = $_search->request('open-ils.search.biblio.record.copy_count' => $one_org->id => $bib);
2038     my $staff_count_req = $_search->request('open-ils.search.biblio.record.copy_count.staff' => $one_org->id => $bib);
2039
2040     my $orgs = $_storage->request(
2041         'open-ils.cstore.json_query.atomic',
2042         { from => [ 'actor.org_unit_descendants', defined($depth) ? ( $one_org->id, $depth ) :  ( $one_org->id ) ] }
2043     )->gather(1);
2044
2045
2046         my @ou_ids = map { $_->{id} } @$orgs;
2047
2048         $logger->info("Searching for holdings at orgs [".join(',',@ou_ids)."], based on $ou");
2049
2050     my %subselect = ( '-or' => [
2051         { owning_lib => \@ou_ids },
2052         { '-exists'  =>
2053             { from  => 'acp',
2054               where => {
2055                 call_number => { '=' => {'+acn'=>'id'} },
2056                 deleted => 'f',
2057                 circ_lib => \@ou_ids
2058               }
2059             }
2060         }
2061     ]);
2062
2063     if ($flesh and $flesh eq 'uris') {
2064         %subselect = (
2065             owning_lib => \@ou_ids,
2066             '-exists'  => {
2067                 from  => { auricnm => 'auri' },
2068                 where => {
2069                     call_number => { '=' => {'+acn'=>'id'} },
2070                     '+auri' => { active => 't' }
2071                 }
2072             }
2073         );
2074     }
2075
2076
2077         my $cns = $_storage->request(
2078                 "open-ils.cstore.direct.asset.call_number.search.atomic",
2079                 { record  => $bib,
2080           deleted => 'f',
2081           %subselect
2082         },
2083                 { flesh         => 5,
2084                   flesh_fields  => {
2085                                         acn     => [qw/copies owning_lib uri_maps prefix suffix/],
2086                                         auricnm => [qw/uri/],
2087                                         acp     => [qw/circ_lib location status stat_cat_entries notes parts/],
2088                                         asce    => [qw/stat_cat/],
2089                                 },
2090           ( $limit > -1 ? ( limit  => $limit  ) : () ),
2091           ( $offset     ? ( offset => $offset ) : () ),
2092           order_by  => { acn => { label_sortkey => {} } }
2093                 }
2094         )->gather(1);
2095
2096         my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
2097         $year += 1900;
2098         $month += 1;
2099
2100         $client->respond("<holdings xmlns='http://open-ils.org/spec/holdings/v1'><counts>\n");
2101
2102         my $copy_counts = $count_req->gather(1);
2103         my $staff_copy_counts = $staff_count_req->gather(1);
2104
2105         for my $c (@$copy_counts) {
2106                 $$c{transcendant} ||= 0;
2107                 my $out = "<count type='public'";
2108                 $out .= " $_='$$c{$_}'" for (qw/count available unshadow transcendant org_unit depth/);
2109                 $client->respond("$out/>\n")
2110         }
2111
2112         for my $c (@$staff_copy_counts) {
2113                 $$c{transcendant} ||= 0;
2114                 my $out = "<count type='staff'";
2115                 $out .= " $_='$$c{$_}'" for (qw/count available unshadow transcendant org_unit depth/);
2116                 $client->respond("$out/>\n")
2117         }
2118
2119     $client->respond("</counts><volumes>\n");
2120     
2121         for my $cn (@$cns) {
2122                 next unless (@{$cn->copies} > 0 or (ref($cn->uri_maps) and @{$cn->uri_maps}));
2123
2124                 # We don't want O:A:S:unAPI::acn to return the record, we've got that already
2125                 # In the context of BibTemplate, copies aren't necessary because we pull those
2126                 # in a separate call
2127         $client->respond(
2128             OpenILS::Application::SuperCat::unAPI::acn
2129                 ->new( $cn )
2130                 ->as_xml( {no_record => 1, no_copies => ($flesh ? 0 : 1)} )
2131         );
2132         }
2133
2134         $client->respond("</volumes><subscriptions>\n");
2135
2136         $logger->info("Searching for serial holdings at orgs [".join(',',@ou_ids)."], based on $ou");
2137
2138     %subselect = ( '-or' => [
2139         { owning_lib => \@ou_ids },
2140         { '-exists'  =>
2141             { from  => 'sdist',
2142               where => { holding_lib => \@ou_ids }
2143             }
2144         }
2145     ]);
2146
2147         my $ssubs = $_storage->request(
2148                 "open-ils.cstore.direct.serial.subscription.search.atomic",
2149                 { record_entry  => $bib,
2150           %subselect
2151         },
2152                 { flesh         => 7,
2153                   flesh_fields  => {
2154                                         ssub    => [qw/distributions issuances scaps owning_lib/],
2155                                         sdist   => [qw/basic_summary supplement_summary index_summary streams holding_lib/],
2156                                         sstr    => [qw/items/],
2157                                         sitem   => [qw/notes unit/],
2158                                         sunit   => [qw/notes location status circ_lib stat_cat_entries call_number/],
2159                                         acn     => [qw/owning_lib prefix suffix/],
2160                                 },
2161           ( $limit > -1 ? ( limit  => $limit  ) : () ),
2162           ( $offset     ? ( offset => $offset ) : () ),
2163           order_by  => {
2164                         ssub => {
2165                                 start_date => {},
2166                                 owning_lib => {},
2167                                 id => {}
2168                         },
2169                         sdist => {
2170                                 label => {},
2171                                 owning_lib => {},
2172                         },
2173                         sunit => {
2174                                 date_expected => {},
2175                         }
2176                   }
2177                 }
2178         )->gather(1);
2179
2180
2181         for my $ssub (@$ssubs) {
2182                 next unless (@{$ssub->distributions} or @{$ssub->issuances} or @{$ssub->scaps});
2183
2184                 # We don't want O:A:S:unAPI::ssub to return the record, we've got that already
2185                 # In the context of BibTemplate, copies aren't necessary because we pull those
2186                 # in a separate call
2187         $client->respond(
2188             OpenILS::Application::SuperCat::unAPI::ssub
2189                 ->new( $ssub )
2190                 ->as_xml( {no_record => 1, no_items => ($flesh ? 0 : 1)} )
2191         );
2192         }
2193
2194
2195         return "</subscriptions></holdings>\n";
2196 }
2197 __PACKAGE__->register_method(
2198         method    => 'new_record_holdings',
2199         api_name  => 'open-ils.supercat.record.holdings_xml.retrieve',
2200         api_level => 1,
2201         argc      => 1,
2202         stream    => 1,
2203         signature =>
2204                 { desc     => <<"                 DESC",
2205 Returns the XML representation of the requested bibliographic record's holdings
2206                   DESC
2207                   params   =>
2208                         [
2209                                 { name => 'bibId',
2210                                   desc => 'An OpenILS biblio::record_entry ID',
2211                                   type => 'number' },
2212                                 { name => 'orgUnit',
2213                                   desc => 'An OpenILS actor::org_unit short name that limits the scope of returned holdings',
2214                                   type => 'text' },
2215                                 { name => 'depth',
2216                                   desc => 'An OpenILS actor::org_unit_type depththat limits the scope of returned holdings',
2217                                   type => 'number' },
2218                                 { name => 'hideCopies',
2219                                   desc => 'Flag that prevents the inclusion of copies in the returned holdings',
2220                                   type => 'boolean' },
2221                                 { name => 'paging',
2222                                   desc => 'Arry of limit and offset for holdings paging',
2223                                   type => 'array' },
2224                         ],
2225                   'return' =>
2226                         { desc => 'Stream of bib record holdings hierarchy in XML',
2227                           type => 'string' }
2228                 }
2229 );
2230
2231 sub isbn_holdings {
2232         my $self = shift;
2233         my $client = shift;
2234         my $isbn = shift;
2235
2236         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2237
2238         my $recs = $_storage->request(
2239                         'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2240                         { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2241         )->gather(1);
2242
2243         return undef unless (@$recs);
2244
2245         return ($self->method_lookup( 'open-ils.supercat.record.holdings_xml.retrieve')->run( $recs->[0]->record ))[0];
2246 }
2247 __PACKAGE__->register_method(
2248         method    => 'isbn_holdings',
2249         api_name  => 'open-ils.supercat.isbn.holdings_xml.retrieve',
2250         api_level => 1,
2251         argc      => 1,
2252         signature =>
2253                 { desc     => <<"                 DESC",
2254 Returns the XML representation of the requested bibliographic record's holdings
2255                   DESC
2256                   params   =>
2257                         [
2258                                 { name => 'isbn',
2259                                   desc => 'An isbn',
2260                                   type => 'string' },
2261                         ],
2262                   'return' =>
2263                         { desc => 'The bib record holdings hierarchy in XML',
2264                           type => 'string' }
2265                 }
2266 );
2267
2268 sub escape {
2269         my $self = shift;
2270         my $text = shift;
2271     return '' unless $text;
2272         $text =~ s/&/&amp;/gsom;
2273         $text =~ s/</&lt;/gsom;
2274         $text =~ s/>/&gt;/gsom;
2275         $text =~ s/"/&quot;/gsom;
2276         $text =~ s/'/&apos;/gsom;
2277         return $text;
2278 }
2279
2280 sub recent_changes {
2281         my $self = shift;
2282         my $client = shift;
2283         my $when = shift || '1-01-01';
2284         my $limit = shift;
2285
2286         my $type = 'biblio';
2287         my $hint = 'bre';
2288
2289         if ($self->api_name =~ /authority/o) {
2290                 $type = 'authority';
2291                 $hint = 'are';
2292         }
2293
2294         my $axis = 'create_date';
2295         $axis = 'edit_date' if ($self->api_name =~ /edit/o);
2296
2297         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2298
2299         return $_storage->request(
2300                 "open-ils.cstore.direct.$type.record_entry.id_list.atomic",
2301                 { $axis => { ">" => $when }, id => { '>' => 0 }, deleted => 'f', active => 't' },
2302                 { order_by => { $hint => "$axis desc" }, limit => $limit }
2303         )->gather(1);
2304 }
2305
2306 for my $t ( qw/biblio authority/ ) {
2307         for my $a ( qw/import edit/ ) {
2308
2309                 __PACKAGE__->register_method(
2310                         method    => 'recent_changes',
2311                         api_name  => "open-ils.supercat.$t.record.$a.recent",
2312                         api_level => 1,
2313                         argc      => 0,
2314                         signature =>
2315                                 { desc     => "Returns a list of recently ${a}ed $t records",
2316                                   params   =>
2317                                         [
2318                                                 { name => 'when',
2319                                                   desc => "Date to start looking for ${a}ed records",
2320                                                   default => '1-01-01',
2321                                                   type => 'string' },
2322
2323                                                 { name => 'limit',
2324                                                   desc => "Maximum count to retrieve",
2325                                                   type => 'number' },
2326                                         ],
2327                                   'return' =>
2328                                         { desc => "An id list of $t records",
2329                                           type => 'array' }
2330                                 },
2331                 );
2332         }
2333 }
2334
2335
2336 sub retrieve_authority_marcxml {
2337         my $self = shift;
2338         my $client = shift;
2339         my $rid = shift;
2340
2341         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2342
2343         my $record = $_storage->request( 'open-ils.cstore.direct.authority.record_entry.retrieve' => $rid )->gather(1);
2344         return $U->entityize( $record->marc ) if ($record);
2345         return undef;
2346 }
2347
2348 __PACKAGE__->register_method(
2349         method    => 'retrieve_authority_marcxml',
2350         api_name  => 'open-ils.supercat.authority.marcxml.retrieve',
2351         api_level => 1,
2352         argc      => 1,
2353         signature =>
2354                 { desc     => <<"                 DESC",
2355 Returns the MARCXML representation of the requested authority record
2356                   DESC
2357                   params   =>
2358                         [
2359                                 { name => 'authorityId',
2360                                   desc => 'An OpenILS authority::record_entry id',
2361                                   type => 'number' },
2362                         ],
2363                   'return' =>
2364                         { desc => 'The authority record in MARCXML',
2365                           type => 'string' }
2366                 }
2367 );
2368
2369 sub retrieve_record_marcxml {
2370         my $self = shift;
2371         my $client = shift;
2372         my $rid = shift;
2373
2374         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2375
2376         my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rid )->gather(1);
2377         return $U->entityize( $record->marc ) if ($record);
2378         return undef;
2379 }
2380
2381 __PACKAGE__->register_method(
2382         method    => 'retrieve_record_marcxml',
2383         api_name  => 'open-ils.supercat.record.marcxml.retrieve',
2384         api_level => 1,
2385         argc      => 1,
2386         signature =>
2387                 { desc     => <<"                 DESC",
2388 Returns the MARCXML representation of the requested bibliographic record
2389                   DESC
2390                   params   =>
2391                         [
2392                                 { name => 'bibId',
2393                                   desc => 'An OpenILS biblio::record_entry id',
2394                                   type => 'number' },
2395                         ],
2396                   'return' =>
2397                         { desc => 'The bib record in MARCXML',
2398                           type => 'string' }
2399                 }
2400 );
2401
2402 sub retrieve_isbn_marcxml {
2403         my $self = shift;
2404         my $client = shift;
2405         my $isbn = shift;
2406
2407         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2408
2409         my $recs = $_storage->request(
2410                         'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2411                         { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2412         )->gather(1);
2413
2414         return undef unless (@$recs);
2415
2416         my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
2417         return $U->entityize( $record->marc ) if ($record);
2418         return undef;
2419 }
2420
2421 __PACKAGE__->register_method(
2422         method    => 'retrieve_isbn_marcxml',
2423         api_name  => 'open-ils.supercat.isbn.marcxml.retrieve',
2424         api_level => 1,
2425         argc      => 1,
2426         signature =>
2427                 { desc     => <<"                 DESC",
2428 Returns the MARCXML representation of the requested ISBN
2429                   DESC
2430                   params   =>
2431                         [
2432                                 { name => 'ISBN',
2433                                   desc => 'An ... um ... ISBN',
2434                                   type => 'string' },
2435                         ],
2436                   'return' =>
2437                         { desc => 'The bib record in MARCXML',
2438                           type => 'string' }
2439                 }
2440 );
2441
2442 sub retrieve_record_transform {
2443         my $self = shift;
2444         my $client = shift;
2445         my $rid = shift;
2446
2447         (my $transform = $self->api_name) =~ s/^.+record\.([^\.]+)\.retrieve$/$1/o;
2448
2449         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2450         #$_storage->connect;
2451
2452         my $record = $_storage->request(
2453                 'open-ils.cstore.direct.biblio.record_entry.retrieve',
2454                 $rid
2455         )->gather(1);
2456
2457         return undef unless ($record);
2458
2459         return $U->entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $record->marc ) )->toString);
2460 }
2461
2462 sub retrieve_isbn_transform {
2463         my $self = shift;
2464         my $client = shift;
2465         my $isbn = shift;
2466
2467         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2468
2469         my $recs = $_storage->request(
2470                         'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2471                         { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2472         )->gather(1);
2473
2474         return undef unless (@$recs);
2475
2476         (my $transform = $self->api_name) =~ s/^.+isbn\.([^\.]+)\.retrieve$/$1/o;
2477
2478         my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
2479
2480         return undef unless ($record);
2481
2482         return $U->entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $record->marc ) )->toString);
2483 }
2484
2485 sub retrieve_record_objects {
2486         my $self = shift;
2487         my $client = shift;
2488         my $ids = shift;
2489
2490         my $type = 'biblio';
2491
2492         if ($self->api_name =~ /authority/) {
2493                 $type = 'authority';
2494         }
2495
2496         $ids = [$ids] unless (ref $ids);
2497         $ids = [grep {$_} @$ids];
2498
2499         return [] unless (@$ids);
2500
2501         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2502         return $_storage->request("open-ils.cstore.direct.$type.record_entry.search.atomic" => { id => [grep {$_} @$ids] })->gather(1);
2503 }
2504 __PACKAGE__->register_method(
2505         method    => 'retrieve_record_objects',
2506         api_name  => 'open-ils.supercat.record.object.retrieve',
2507         api_level => 1,
2508         argc      => 1,
2509         signature =>
2510                 { desc     => <<"                 DESC",
2511 Returns the Fieldmapper object representation of the requested bibliographic records
2512                   DESC
2513                   params   =>
2514                         [
2515                                 { name => 'bibIds',
2516                                   desc => 'OpenILS biblio::record_entry ids',
2517                                   type => 'array' },
2518                         ],
2519                   'return' =>
2520                         { desc => 'The bib records',
2521                           type => 'array' }
2522                 }
2523 );
2524
2525 __PACKAGE__->register_method(
2526         method    => 'retrieve_record_objects',
2527         api_name  => 'open-ils.supercat.authority.object.retrieve',
2528         api_level => 1,
2529         argc      => 1,
2530         signature =>
2531                 { desc     => <<"                 DESC",
2532 Returns the Fieldmapper object representation of the requested authority records
2533                   DESC
2534                   params   =>
2535                         [
2536                                 { name => 'authIds',
2537                                   desc => 'OpenILS authority::record_entry ids',
2538                                   type => 'array' },
2539                         ],
2540                   'return' =>
2541                         { desc => 'The authority records',
2542                           type => 'array' }
2543                 }
2544 );
2545
2546 sub retrieve_isbn_object {
2547         my $self = shift;
2548         my $client = shift;
2549         my $isbn = shift;
2550
2551         return undef unless ($isbn);
2552
2553         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2554         my $recs = $_storage->request(
2555                         'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2556                         { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2557         )->gather(1);
2558
2559         return undef unless (@$recs);
2560
2561         return $_storage->request(
2562                 'open-ils.cstore.direct.biblio.record_entry.search.atomic',
2563                 { id => $recs->[0]->record }
2564         )->gather(1);
2565 }
2566 __PACKAGE__->register_method(
2567         method    => 'retrieve_isbn_object',
2568         api_name  => 'open-ils.supercat.isbn.object.retrieve',
2569         api_level => 1,
2570         argc      => 1,
2571         signature =>
2572                 { desc     => <<"                 DESC",
2573 Returns the Fieldmapper object representation of the requested bibliographic record
2574                   DESC
2575                   params   =>
2576                         [
2577                                 { name => 'isbn',
2578                                   desc => 'an ISBN',
2579                                   type => 'string' },
2580                         ],
2581                   'return' =>
2582                         { desc => 'The bib record',
2583                           type => 'object' }
2584                 }
2585 );
2586
2587
2588
2589 sub retrieve_metarecord_mods {
2590         my $self = shift;
2591         my $client = shift;
2592         my $rid = shift;
2593
2594         my $_storage = OpenSRF::AppSession->connect( 'open-ils.cstore' );
2595
2596         # Get the metarecord in question
2597         my $mr =
2598         $_storage->request(
2599                 'open-ils.cstore.direct.metabib.metarecord.retrieve' => $rid
2600         )->gather(1);
2601
2602         # Now get the map of all bib records for the metarecord
2603         my $recs =
2604         $_storage->request(
2605                 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
2606                 {metarecord => $rid}
2607         )->gather(1);
2608
2609         $logger->debug("Adding ".scalar(@$recs)." bib record to the MODS of the metarecord");
2610
2611         # and retrieve the lead (master) record as MODS
2612         my ($master) =
2613                 $self   ->method_lookup('open-ils.supercat.record.mods.retrieve')
2614                         ->run($mr->master_record);
2615         my $master_mods = $_parser->parse_string($master)->documentElement;
2616         $master_mods->setNamespace( "http://www.loc.gov/mods/", "mods" );
2617         $master_mods->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2618
2619         # ... and a MODS clone to populate, with guts removed.
2620         my $mods = $_parser->parse_string($master)->documentElement;
2621         $mods->setNamespace( "http://www.loc.gov/mods/", "mods" ); # modsCollection element
2622         $mods->setNamespace('http://www.loc.gov/mods/', undef, 1);
2623         ($mods) = $mods->findnodes('//mods:mods');
2624         #$mods->setNamespace( "http://www.loc.gov/mods/", "mods" ); # mods element
2625         $mods->removeChildNodes;
2626         $mods->setNamespace('http://www.loc.gov/mods/', undef, 1);
2627
2628         # Add the metarecord ID as a (locally defined) info URI
2629         my $recordInfo = $mods
2630                 ->ownerDocument
2631                 ->createElement("recordInfo");
2632
2633         my $recordIdentifier = $mods
2634                 ->ownerDocument
2635                 ->createElement("recordIdentifier");
2636
2637         my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
2638         $year += 1900;
2639         $month += 1;
2640
2641         my $id = $mr->id;
2642         $recordIdentifier->appendTextNode(
2643                 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:metabib-metarecord/$id", $month, $day)
2644         );
2645
2646         $recordInfo->appendChild($recordIdentifier);
2647         $mods->appendChild($recordInfo);
2648
2649         # Grab the title, author and ISBN for the master record and populate the metarecord
2650         my ($title) = $master_mods->findnodes( './mods:titleInfo[not(@type)]' );
2651         
2652         if ($title) {
2653                 $title->setNamespace( "http://www.loc.gov/mods/", "mods" );
2654                 $title->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2655                 $title = $mods->ownerDocument->importNode($title);
2656                 $mods->appendChild($title);
2657         }
2658
2659         my ($author) = $master_mods->findnodes( './mods:name[mods:role/mods:text[text()="creator"]]' );
2660         if ($author) {
2661                 $author->setNamespace( "http://www.loc.gov/mods/", "mods" );
2662                 $author->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2663                 $author = $mods->ownerDocument->importNode($author);
2664                 $mods->appendChild($author);
2665         }
2666
2667         my ($isbn) = $master_mods->findnodes( './mods:identifier[@type="isbn"]' );
2668         if ($isbn) {
2669                 $isbn->setNamespace( "http://www.loc.gov/mods/", "mods" );
2670                 $isbn->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2671                 $isbn = $mods->ownerDocument->importNode($isbn);
2672                 $mods->appendChild($isbn);
2673         }
2674
2675         # ... and loop over the constituent records
2676         for my $map ( @$recs ) {
2677
2678                 # get the MODS
2679                 my ($rec) =
2680                         $self   ->method_lookup('open-ils.supercat.record.mods.retrieve')
2681                                 ->run($map->source);
2682
2683                 my $part_mods = $_parser->parse_string($rec);
2684                 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", "mods" );
2685                 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2686                 ($part_mods) = $part_mods->findnodes('//mods:mods');
2687
2688                 for my $node ( ($part_mods->findnodes( './mods:subject' )) ) {
2689                         $node->setNamespace( "http://www.loc.gov/mods/", "mods" );
2690                         $node->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2691                         $node = $mods->ownerDocument->importNode($node);
2692                         $mods->appendChild( $node );
2693                 }
2694
2695                 my $relatedItem = $mods
2696                         ->ownerDocument
2697                         ->createElement("relatedItem");
2698
2699                 $relatedItem->setAttribute( type => 'constituent' );
2700
2701                 my $identifier = $mods
2702                         ->ownerDocument
2703                         ->createElement("identifier");
2704
2705                 $identifier->setAttribute( type => 'uri' );
2706
2707                 my $subRecordInfo = $mods
2708                         ->ownerDocument
2709                         ->createElement("recordInfo");
2710
2711                 my $subRecordIdentifier = $mods
2712                         ->ownerDocument
2713                         ->createElement("recordIdentifier");
2714
2715                 my $subid = $map->source;
2716                 $subRecordIdentifier->appendTextNode(
2717                         sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:biblio-record_entry/$subid",
2718                                 $month,
2719                                 $day
2720                         )
2721                 );
2722                 $subRecordInfo->appendChild($subRecordIdentifier);
2723
2724                 $relatedItem->appendChild( $subRecordInfo );
2725
2726                 my ($tor) = $part_mods->findnodes( './mods:typeOfResource' );
2727                 $tor->setNamespace( "http://www.loc.gov/mods/", "mods" );
2728                 $tor->setNamespace( "http://www.loc.gov/mods/", undef, 1 ) if ($tor);
2729                 $tor = $mods->ownerDocument->importNode($tor) if ($tor);
2730                 $relatedItem->appendChild($tor) if ($tor);
2731
2732                 if ( my ($part_isbn) = $part_mods->findnodes( './mods:identifier[@type="isbn"]' ) ) {
2733                         $part_isbn->setNamespace( "http://www.loc.gov/mods/", "mods" );
2734                         $part_isbn->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2735                         $part_isbn = $mods->ownerDocument->importNode($part_isbn);
2736                         $relatedItem->appendChild( $part_isbn );
2737
2738                         if (!$isbn) {
2739                                 $isbn = $mods->appendChild( $part_isbn->cloneNode(1) );
2740                         }
2741                 }
2742
2743                 $mods->appendChild( $relatedItem );
2744
2745         }
2746
2747         $_storage->disconnect;
2748
2749         return $U->entityize($mods->toString);
2750
2751 }
2752 __PACKAGE__->register_method(
2753         method    => 'retrieve_metarecord_mods',
2754         api_name  => 'open-ils.supercat.metarecord.mods.retrieve',
2755         api_level => 1,
2756         argc      => 1,
2757         signature =>
2758                 { desc     => <<"                 DESC",
2759 Returns the MODS representation of the requested metarecord
2760                   DESC
2761                   params   =>
2762                         [
2763                                 { name => 'metarecordId',
2764                                   desc => 'An OpenILS metabib::metarecord id',
2765                                   type => 'number' },
2766                         ],
2767                   'return' =>
2768                         { desc => 'The metarecord in MODS',
2769                           type => 'string' }
2770                 }
2771 );
2772
2773 sub list_metarecord_formats {
2774         my @list = (
2775                 { mods =>
2776                         { namespace_uri   => 'http://www.loc.gov/mods/',
2777                           docs            => 'http://www.loc.gov/mods/',
2778                           schema_location => 'http://www.loc.gov/standards/mods/mods.xsd',
2779                         }
2780                 }
2781         );
2782
2783         for my $type ( keys %metarecord_xslt ) {
2784                 push @list,
2785                         { $type => 
2786                                 { namespace_uri   => $metarecord_xslt{$type}{namespace_uri},
2787                                   docs            => $metarecord_xslt{$type}{docs},
2788                                   schema_location => $metarecord_xslt{$type}{schema_location},
2789                                 }
2790                         };
2791         }
2792
2793         return \@list;
2794 }
2795 __PACKAGE__->register_method(
2796         method    => 'list_metarecord_formats',
2797         api_name  => 'open-ils.supercat.metarecord.formats',
2798         api_level => 1,
2799         argc      => 0,
2800         signature =>
2801                 { desc     => <<"                 DESC",
2802 Returns the list of valid metarecord formats that supercat understands.
2803                   DESC
2804                   'return' =>
2805                         { desc => 'The format list',
2806                           type => 'array' }
2807                 }
2808 );
2809
2810
2811 sub list_authority_formats {
2812         my @list = (
2813                 { marcxml =>
2814                         { namespace_uri   => 'http://www.loc.gov/MARC21/slim',
2815                           docs            => 'http://www.loc.gov/marcxml/',
2816                           schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
2817                         }
2818                 }
2819         );
2820
2821 #       for my $type ( keys %record_xslt ) {
2822 #               push @list,
2823 #                       { $type => 
2824 #                               { namespace_uri   => $record_xslt{$type}{namespace_uri},
2825 #                                 docs            => $record_xslt{$type}{docs},
2826 #                                 schema_location => $record_xslt{$type}{schema_location},
2827 #                               }
2828 #                       };
2829 #       }
2830 #
2831         return \@list;
2832 }
2833 __PACKAGE__->register_method(
2834         method    => 'list_authority_formats',
2835         api_name  => 'open-ils.supercat.authority.formats',
2836         api_level => 1,
2837         argc      => 0,
2838         signature =>
2839                 { desc     => <<"                 DESC",
2840 Returns the list of valid authority formats that supercat understands.
2841                   DESC
2842                   'return' =>
2843                         { desc => 'The format list',
2844                           type => 'array' }
2845                 }
2846 );
2847
2848 sub list_record_formats {
2849         my @list = (
2850                 { marcxml =>
2851                         { namespace_uri   => 'http://www.loc.gov/MARC21/slim',
2852                           docs            => 'http://www.loc.gov/marcxml/',
2853                           schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
2854                         }
2855                 }
2856         );
2857
2858         for my $type ( keys %record_xslt ) {
2859                 push @list,
2860                         { $type => 
2861                                 { namespace_uri   => $record_xslt{$type}{namespace_uri},
2862                                   docs            => $record_xslt{$type}{docs},
2863                                   schema_location => $record_xslt{$type}{schema_location},
2864                                 }
2865                         };
2866         }
2867
2868         return \@list;
2869 }
2870 __PACKAGE__->register_method(
2871         method    => 'list_record_formats',
2872         api_name  => 'open-ils.supercat.record.formats',
2873         api_level => 1,
2874         argc      => 0,
2875         signature =>
2876                 { desc     => <<"                 DESC",
2877 Returns the list of valid record formats that supercat understands.
2878                   DESC
2879                   'return' =>
2880                         { desc => 'The format list',
2881                           type => 'array' }
2882                 }
2883 );
2884 __PACKAGE__->register_method(
2885         method    => 'list_record_formats',
2886         api_name  => 'open-ils.supercat.isbn.formats',
2887         api_level => 1,
2888         argc      => 0,
2889         signature =>
2890                 { desc     => <<"                 DESC",
2891 Returns the list of valid record formats that supercat understands.
2892                   DESC
2893                   'return' =>
2894                         { desc => 'The format list',
2895                           type => 'array' }
2896                 }
2897 );
2898
2899
2900 sub oISBN {
2901         my $self = shift;
2902         my $client = shift;
2903         my $isbn = shift;
2904
2905         $isbn =~ s/-//gso;
2906
2907         throw OpenSRF::EX::InvalidArg ('I need an ISBN please')
2908                 unless (length($isbn) >= 10);
2909
2910         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2911
2912         # Create a storage session, since we'll be making muliple requests.
2913         $_storage->connect;
2914
2915         # Find the record that has that ISBN.
2916         my $bibrec = $_storage->request(
2917                 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2918                 { tag => '020', subfield => 'a', value => { like => lc($isbn).'%'} }
2919         )->gather(1);
2920
2921         # Go away if we don't have one.
2922         return {} unless (@$bibrec);
2923
2924         # Find the metarecord for that bib record.
2925         my $mr = $_storage->request(
2926                 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
2927                 {source => $bibrec->[0]->record}
2928         )->gather(1);
2929
2930         # Find the other records for that metarecord.
2931         my $records = $_storage->request(
2932                 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
2933                 {metarecord => $mr->[0]->metarecord}
2934         )->gather(1);
2935
2936         # Just to be safe.  There's currently no unique constraint on sources...
2937         my %unique_recs = map { ($_->source, 1) } @$records;
2938         my @rec_list = sort keys %unique_recs;
2939
2940         # And now fetch the ISBNs for thos records.
2941         my $recs = [];
2942         push @$recs,
2943                 $_storage->request(
2944                         'open-ils.cstore.direct.metabib.full_rec.search',
2945                         { tag => '020', subfield => 'a', record => $_ }
2946                 )->gather(1) for (@rec_list);
2947
2948         # We're done with the storage server session.
2949         $_storage->disconnect;
2950
2951         # Return the oISBN data structure.  This will be XMLized at a higher layer.
2952         return
2953                 { metarecord => $mr->[0]->metarecord,
2954                   record_list => { map { $_ ? ($_->record, $_->value) : () } @$recs } };
2955
2956 }
2957 __PACKAGE__->register_method(
2958         method    => 'oISBN',
2959         api_name  => 'open-ils.supercat.oisbn',
2960         api_level => 1,
2961         argc      => 1,
2962         signature =>
2963                 { desc     => <<"                 DESC",
2964 Returns the ISBN list for the metarecord of the requested isbn
2965                   DESC
2966                   params   =>
2967                         [
2968                                 { name => 'isbn',
2969                                   desc => 'An ISBN.  Duh.',
2970                                   type => 'string' },
2971                         ],
2972                   'return' =>
2973                         { desc => 'record to isbn map',
2974                           type => 'object' }
2975                 }
2976 );
2977
2978 sub return_bib_search_aliases {
2979     my %aliases;
2980
2981         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2982
2983         my $cmsa = $_storage->request(
2984                 'open-ils.cstore.direct.config.metabib_search_alias.search.atomic',
2985                 { alias => { '!=' => undef } }
2986         )->gather(1);
2987
2988     foreach (@$cmsa) {
2989         if ($_->alias =~ /\./) {
2990             my ($qualifier, $name) = $_->alias =~ m/^(.+?)\.(.+)$/;
2991             $aliases{$qualifier}{$name}{'index'} = $_->alias;
2992             # We will add a 'title' property in a subsequent schema
2993             $aliases{$qualifier}{$name}{'title'} = $name;
2994         } else {
2995             # au/kw/se/su/ti go into the default 'eg' qualifier
2996             $aliases{'eg'}{$_->alias}{'index'} = $_->alias;
2997             $aliases{'eg'}{$_->alias}{'title'} = $_->alias;
2998         }
2999     }
3000
3001     return \%aliases;
3002 }
3003
3004 __PACKAGE__->register_method(
3005         method    => 'return_bib_search_aliases',
3006         api_name  => 'open-ils.supercat.biblio.search_aliases',
3007         api_level => 1,
3008         argc      => 0,
3009         signature =>
3010                 { desc     => <<"                 DESC",
3011 Returns the set of qualified search aliases in the system
3012                   DESC
3013                   params   => [ ],
3014                   'return' =>
3015                         { desc => 'Hash of qualified search aliases',
3016                           type => 'object' }
3017                 }
3018 );
3019
3020
3021 package OpenILS::Application::SuperCat::unAPI;
3022 use base qw/OpenILS::Application::SuperCat/;
3023
3024 sub as_xml {
3025     die "dummy superclass, use a real class";
3026 }
3027
3028 sub new {
3029     my $class = shift;
3030     my $obj = shift;
3031     return unless ($obj);
3032
3033     $class = ref($class) || $class;
3034
3035     if ($class eq __PACKAGE__) {
3036         return unless (ref($obj));
3037         $class .= '::' . $obj->json_hint;
3038     }
3039
3040     return bless { obj => $obj } => $class;
3041 }
3042
3043 sub obj {
3044     my $self = shift;
3045     return $self->{obj};
3046 }
3047
3048 package OpenILS::Application::SuperCat::unAPI::auri;
3049 use base qw/OpenILS::Application::SuperCat::unAPI/;
3050
3051 sub as_xml {
3052     my $self = shift;
3053     my $args = shift;
3054
3055     my $xml = '      <uri xmlns="http://open-ils.org/spec/holdings/v1" ';
3056     $xml .= 'id="tag:open-ils.org:asset-uri/' . $self->obj->id . '" ';
3057     $xml .= 'use_restriction="' . $self->escape( $self->obj->use_restriction ) . '" ';
3058     $xml .= 'label="' . $self->escape( $self->obj->label ) . '" ';
3059     $xml .= 'href="' . $self->escape( $self->obj->href ) . '">';
3060
3061     if (!$args->{no_volumes}) {
3062         if (ref($self->obj->call_number_maps) && @{ $self->obj->call_number_maps }) {
3063             $xml .= "      <volumes>\n" . join(
3064                 '',
3065                 map {
3066                     OpenILS::Application::SuperCat::unAPI
3067                         ->new( $_->call_number )
3068                         ->as_xml({ %$args, no_uris=>1, no_copies=>1 })
3069                 } @{ $self->obj->call_number_maps }
3070             ) . "      </volumes>\n";
3071
3072         } else {
3073             $xml .= "      <volumes/>\n";
3074         }
3075     }
3076
3077     $xml .= "      </uri>\n";
3078
3079     return $xml;
3080 }
3081
3082 package OpenILS::Application::SuperCat::unAPI::acn;
3083 use base qw/OpenILS::Application::SuperCat::unAPI/;
3084
3085 sub as_xml {
3086     my $self = shift;
3087     my $args = shift;
3088
3089     my $xml = '    <volume xmlns="http://open-ils.org/spec/holdings/v1" ';
3090
3091     $xml .= 'id="tag:open-ils.org:asset-call_number/' . $self->obj->id . '" ';
3092     $xml .= 'lib="' . $self->escape( $self->obj->owning_lib->shortname ) . '" ';
3093     $xml .= 'opac_visible="' . $self->obj->owning_lib->opac_visible . '" ';
3094     $xml .= 'deleted="' . $self->obj->deleted . '" ';
3095     $xml .= 'label="' . $self->escape( $self->obj->label ) . '">';
3096     $xml .= "\n";
3097
3098     if (!$args->{no_copies}) {
3099         if (ref($self->obj->copies) && @{ $self->obj->copies }) {
3100             $xml .= "      <copies>\n" . join(
3101                 '',
3102                 map {
3103                     OpenILS::Application::SuperCat::unAPI
3104                         ->new( $_ )
3105                         ->as_xml({ %$args, no_volume=>1 })
3106                 } @{ $self->obj->copies }
3107             ) . "      </copies>\n";
3108
3109         } else {
3110             $xml .= "      <copies/>\n";
3111         }
3112     }
3113
3114     if (!$args->{no_uris}) {
3115         if (ref($self->obj->uri_maps) && @{ $self->obj->uri_maps }) {
3116             $xml .= "      <uris>\n" . join(
3117                 '',
3118                 map {
3119                     OpenILS::Application::SuperCat::unAPI
3120                         ->new( $_->uri )
3121                         ->as_xml({ %$args, no_volumes=>1 })
3122                 } @{ $self->obj->uri_maps }
3123             ) . "      </uris>\n";
3124
3125         } else {
3126             $xml .= "      <uris/>\n";
3127         }
3128     }
3129
3130
3131     $xml .= '      <prefix ';
3132     $xml .= 'ident="' . $self->obj->prefix->id . '" ';
3133     $xml .= 'id="tag:open-ils.org:asset-call_number_prefix/' . $self->obj->prefix->id . '" ';
3134     $xml .= 'label_sortkey="'.$self->escape( $self->obj->prefix->label_sortkey ) .'">';
3135     $xml .= $self->escape( $self->obj->prefix->label ) .'</prefix>';
3136     $xml .= "\n";
3137
3138     $xml .= '      <suffix ';
3139     $xml .= 'ident="' . $self->obj->suffix->id . '" ';
3140     $xml .= 'id="tag:open-ils.org:asset-call_number_suffix/' . $self->obj->suffix->id . '" ';
3141     $xml .= 'label_sortkey="'.$self->escape( $self->obj->suffix->label_sortkey ) .'">';
3142     $xml .= $self->escape( $self->obj->suffix->label ) .'</suffix>';
3143     $xml .= "\n";
3144
3145     $xml .= '      <owning_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3146     $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->owning_lib->id . '" ';
3147     $xml .= 'shortname="'.$self->escape( $self->obj->owning_lib->shortname ) .'" ';
3148     $xml .= 'name="'.$self->escape( $self->obj->owning_lib->name ) .'"/>';
3149     $xml .= "\n";
3150
3151     unless ($args->{no_record}) {
3152         my $rec_tag = "tag:open-ils.org:biblio-record_entry/".$self->obj->record->id.'/'.$self->escape( $self->obj->owning_lib->shortname ) ;
3153
3154         my $r_doc = $parser->parse_string($self->obj->record->marc);
3155         $r_doc->documentElement->setAttribute( id => $rec_tag );
3156         $xml .= $U->entityize($r_doc->documentElement->toString);
3157     }
3158
3159     $xml .= "    </volume>\n";
3160
3161     return $xml;
3162 }
3163
3164 package OpenILS::Application::SuperCat::unAPI::ssub;
3165 use base qw/OpenILS::Application::SuperCat::unAPI/;
3166
3167 sub as_xml {
3168     my $self = shift;
3169     my $args = shift;
3170
3171     my $xml = '    <subscription xmlns="http://open-ils.org/spec/holdings/v1" ';
3172
3173     $xml .= 'id="tag:open-ils.org:serial-subscription/' . $self->obj->id . '" ';
3174     $xml .= 'start="' . $self->escape( $self->obj->start_date ) . '" ';
3175     $xml .= 'end="' . $self->escape( $self->obj->end_date ) . '" ';
3176     $xml .= 'expected_date_offset="' . $self->escape( $self->obj->expected_date_offset ) . '">';
3177     $xml .= "\n";
3178
3179     if (!$args->{no_distributions}) {
3180         if (ref($self->obj->distributions) && @{ $self->obj->distributions }) {
3181             $xml .= "      <distributions>\n" . join(
3182                 '',
3183                 map {
3184                     OpenILS::Application::SuperCat::unAPI
3185                         ->new( $_ )
3186                         ->as_xml({ %$args, no_subscription=>1, no_issuance=>1 })
3187                 } @{ $self->obj->distributions }
3188             ) . "      </distributions>\n";
3189
3190         } else {
3191             $xml .= "      <distributions/>\n";
3192         }
3193     }
3194
3195     if (!$args->{no_captions_and_patterns}) {
3196         if (ref($self->obj->scaps) && @{ $self->obj->scaps }) {
3197             $xml .= "      <captions_and_patterns>\n" . join(
3198                 '',
3199                 map {
3200                     OpenILS::Application::SuperCat::unAPI
3201                         ->new( $_ )
3202                         ->as_xml({ %$args, no_subscription=>1 })
3203                 } @{ $self->obj->scaps }
3204             ) . "      </captions_and_patterns>\n";
3205
3206         } else {
3207             $xml .= "      <captions_and_patterns/>\n";
3208         }
3209     }
3210
3211     if (!$args->{no_issuances}) {
3212         if (ref($self->obj->issuances) && @{ $self->obj->issuances }) {
3213             $xml .= "      <issuances>\n" . join(
3214                 '',
3215                 map {
3216                     OpenILS::Application::SuperCat::unAPI
3217                         ->new( $_ )
3218                         ->as_xml({ %$args, no_subscription=>1, no_items=>1 })
3219                 } @{ $self->obj->issuances }
3220             ) . "      </issuances>\n";
3221
3222         } else {
3223             $xml .= "      <issuances/>\n";
3224         }
3225     }
3226
3227
3228     $xml .= '      <owning_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3229     $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->owning_lib->id . '" ';
3230     $xml .= 'shortname="'.$self->escape( $self->obj->owning_lib->shortname ) .'" ';
3231     $xml .= 'name="'.$self->escape( $self->obj->owning_lib->name ) .'"/>';
3232     $xml .= "\n";
3233
3234     unless ($args->{no_record}) {
3235         my $rec_tag = "tag:open-ils.org:biblio-record_entry/".$self->obj->record->id.'/'.$self->escape( $self->obj->owning_lib->shortname ) ;
3236
3237         my $r_doc = $parser->parse_string($self->obj->record_entry->marc);
3238         $r_doc->documentElement->setAttribute( id => $rec_tag );
3239         $xml .= $U->entityize($r_doc->documentElement->toString);
3240     }
3241
3242     $xml .= "    </subscription>\n";
3243
3244     return $xml;
3245 }
3246
3247 package OpenILS::Application::SuperCat::unAPI::ssum_base;
3248 use base qw/OpenILS::Application::SuperCat::unAPI/;
3249
3250 sub as_xml {
3251     my $self = shift;
3252     my $args = shift;
3253
3254     (my $type = ref($self)) =~ s/^.+([^:]+)$/$1/;
3255
3256     my $xml = "    <serial_summary xmlns=\"http://open-ils.org/spec/holdings/v1\" type=\"$type\" ";
3257
3258     $xml .= "id=\"tag:open-ils.org:serial-summary-$type/" . $self->obj->id . '" ';
3259     $xml .= 'generated_coverage="' . $self->escape( $self->obj->generated_coverage ) . '" ';
3260     $xml .= 'show_generated="' . $self->escape( $self->obj->show_generated ) . '" ';
3261     $xml .= 'textual_holdings="' . $self->escape( $self->obj->textual_holdings ) . '">';
3262     $xml .= "\n";
3263
3264         $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->distribution )->as_xml({ %$args, no_summaries=>1 }) if (!$args->{no_distribution});
3265
3266     $xml .= "    </serial_summary>\n";
3267
3268     return $xml;
3269 }
3270
3271
3272 package OpenILS::Application::SuperCat::unAPI::sssum;
3273 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3274
3275 package OpenILS::Application::SuperCat::unAPI::sbsum;
3276 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3277
3278 package OpenILS::Application::SuperCat::unAPI::sisum;
3279 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
3280
3281 package OpenILS::Application::SuperCat::unAPI::sdist;
3282 use base qw/OpenILS::Application::SuperCat::unAPI/;
3283
3284 sub as_xml {
3285     my $self = shift;
3286     my $args = shift;
3287
3288     my $xml = '    <distribution xmlns="http://open-ils.org/spec/holdings/v1" ';
3289
3290     $xml .= 'id="tag:open-ils.org:serial-distribution/' . $self->obj->id . '" ';
3291     $xml .= 'label="' . $self->escape( $self->obj->label ) . '" ';
3292     $xml .= 'unit_label_prefix="' . $self->escape( $self->obj->unit_label_prefix ) . '" ';
3293     $xml .= 'unit_label_suffix="' . $self->escape( $self->obj->unit_label_suffix ) . '">';
3294     $xml .= "\n";
3295
3296     if (!$args->{no_distributions}) {
3297         if (ref($self->obj->streams) && @{ $self->obj->streams }) {
3298             $xml .= "      <streams>\n" . join(
3299                 '',
3300                 map {
3301                     OpenILS::Application::SuperCat::unAPI
3302                         ->new( $_ )
3303                         ->as_xml({ %$args, no_distribution=>1 })
3304                 } @{ $self->obj->streams }
3305             ) . "      </streams>\n";
3306
3307         } else {
3308             $xml .= "      <streams/>\n";
3309         }
3310     }
3311
3312     if (!$args->{no_summaries}) {
3313         $xml .= "      <summaries>\n";
3314         $xml .= join ('',
3315         map {
3316             defined $_ ?
3317                 OpenILS::Application::SuperCat::unAPI
3318                 ->new( $_ )
3319                 ->as_xml({ %$args, no_distribution=>1 }) : ""
3320         } ($self->obj->basic_summary, $self->obj->supplement_summary, $self->obj->index_summary)
3321         );
3322
3323         $xml .= "      </summaries>\n";
3324     }
3325
3326
3327     $xml .= '      <holding_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3328     $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->holding_lib->id . '" ';
3329     $xml .= 'shortname="'.$self->escape( $self->obj->holding_lib->shortname ) .'" ';
3330     $xml .= 'name="'.$self->escape( $self->obj->holding_lib->name ) .'"/>';
3331     $xml .= "\n";
3332
3333         $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_distributions=>1 }) if (!$args->{no_subscription});
3334
3335     if (!$args->{no_record} && $self->obj->record_entry) {
3336         my $rec_tag = "tag:open-ils.org:serial-record_entry/".$self->obj->record_entry->id ;
3337
3338         my $r_doc = $parser->parse_string($self->obj->record_entry->marc);
3339         $r_doc->documentElement->setAttribute( id => $rec_tag );
3340         $xml .= $U->entityize($r_doc->documentElement->toString);
3341     }
3342
3343     $xml .= "    </distribution>\n";
3344
3345     return $xml;
3346 }
3347
3348 package OpenILS::Application::SuperCat::unAPI::sstr;
3349 use base qw/OpenILS::Application::SuperCat::unAPI/;
3350
3351 sub as_xml {
3352     my $self = shift;
3353     my $args = shift;
3354
3355     my $xml = '    <stream xmlns="http://open-ils.org/spec/holdings/v1" ';
3356
3357     $xml .= 'id="tag:open-ils.org:serial-stream/' . $self->obj->id . '" ';
3358     $xml .= 'routing_label="' . $self->escape( $self->obj->routing_label ) . '">';
3359     $xml .= "\n";
3360
3361     if (!$args->{no_items}) {
3362         if (ref($self->obj->items) && @{ $self->obj->items }) {
3363             $xml .= "      <items>\n" . join(
3364                 '',
3365                 map {
3366                     OpenILS::Application::SuperCat::unAPI
3367                         ->new( $_ )
3368                         ->as_xml({ %$args, no_stream=>1 })
3369                 } @{ $self->obj->items }
3370             ) . "      </items>\n";
3371
3372         } else {
3373             $xml .= "      <items/>\n";
3374         }
3375     }
3376
3377         #XXX routing_list_user's?
3378
3379         $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->distribution )->as_xml({ %$args, no_streams=>1 }) if (!$args->{no_distribution});
3380
3381     $xml .= "    </stream>\n";
3382
3383     return $xml;
3384 }
3385
3386 package OpenILS::Application::SuperCat::unAPI::sitem;
3387 use base qw/OpenILS::Application::SuperCat::unAPI/;
3388
3389 sub as_xml {
3390     my $self = shift;
3391     my $args = shift;
3392
3393     my $xml = '    <serial_item xmlns="http://open-ils.org/spec/holdings/v1" ';
3394
3395     $xml .= 'id="tag:open-ils.org:serial-item/' . $self->obj->id . '" ';
3396     $xml .= 'date_expected="' . $self->escape( $self->obj->date_expected ) . '"';
3397     $xml .= ' date_received="' . $self->escape( $self->obj->date_received ) .'"'if ($self->obj->date_received);
3398
3399         if ($args->{no_issuance}) {
3400                 my $siss = ref($self->obj->issuance) ? $self->obj->issuance->id : $self->obj->issuance;
3401             $xml .= ' issuance="tag:open-ils.org:serial-issuance/' . $siss . '"';
3402         }
3403
3404     $xml .= ">\n";
3405
3406         if (ref($self->obj->notes) && $self->obj->notes) {
3407                 $xml .= "        <notes>\n";
3408                 for my $note ( @{$self->obj->notes} ) {
3409                         next unless ( $note->pub eq 't' );
3410                         $xml .= sprintf('        <note date="%s" title="%s">%s</note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3411                         $xml .= "\n";
3412                 }
3413                 $xml .= "        </notes>\n";
3414     } else {
3415         $xml .= "      <notes/>\n";
3416         }
3417
3418         $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->issuance )->as_xml({ %$args, no_items=>1 }) if (!$args->{no_issuance});
3419         $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->stream )->as_xml({ %$args, no_items=>1 }) if (!$args->{no_stream});
3420         $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});
3421         $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});
3422
3423     $xml .= "    </serial_item>\n";
3424
3425     return $xml;
3426 }
3427
3428 package OpenILS::Application::SuperCat::unAPI::sunit;
3429 use base qw/OpenILS::Application::SuperCat::unAPI/;
3430
3431 sub as_xml {
3432     my $self = shift;
3433     my $args = shift;
3434
3435     my $xml = '      <serial_unit xmlns="http://open-ils.org/spec/holdings/v1" '.
3436         'id="tag:open-ils.org:serial-unit/' . $self->obj->id . '" ';
3437
3438     $xml .= $_ . '="' . $self->escape( $self->obj->$_  ) . '" ' for (qw/
3439         create_date edit_date copy_number circulate deposit ref holdable deleted
3440         deposit_amount price barcode circ_modifier circ_as_type opac_visible cost
3441         status_changed_time floating mint_condition detailed_contents sort_key summary_contents
3442     /);
3443
3444     $xml .= ">\n";
3445
3446     $xml .= '        <status ident="' . $self->obj->status->id . '" opac_visible="' . $self->obj->status->opac_visible . '">' . $self->escape( $self->obj->status->name  ) . "</status>\n";
3447     $xml .= '        <location ident="' . $self->obj->location->id . '">' . $self->escape( $self->obj->location->name  ) . "</location>\n";
3448     $xml .= '        <circlib ident="' . $self->obj->circ_lib->id . '">' . $self->escape( $self->obj->circ_lib->name  ) . "</circlib>\n";
3449
3450     $xml .= '        <circ_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3451     $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->circ_lib->id . '" ';
3452     $xml .= 'shortname="'.$self->escape( $self->obj->circ_lib->shortname ) .'" ';
3453     $xml .= 'name="'.$self->escape( $self->obj->circ_lib->name ) .'"/>';
3454     $xml .= "\n";
3455
3456         $xml .= "        <copy_notes>\n";
3457         if (ref($self->obj->notes) && $self->obj->notes) {
3458                 for my $note ( @{$self->obj->notes} ) {
3459                         next unless ( $note->pub eq 't' );
3460                         $xml .= sprintf('        <copy_note date="%s" title="%s">%s</copy_note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3461                         $xml .= "\n";
3462                 }
3463         }
3464
3465         $xml .= "        </copy_notes>\n";
3466     $xml .= "        <statcats>\n";
3467
3468         if (ref($self->obj->stat_cat_entries) && $self->obj->stat_cat_entries) {
3469                 for my $sce ( @{$self->obj->stat_cat_entries} ) {
3470                         next unless ( $sce->stat_cat->opac_visible eq 't' );
3471                         $xml .= sprintf('          <statcat name="%s">%s</statcat>',$self->escape($sce->stat_cat->name) ,$self->escape($sce->value));
3472                         $xml .= "\n";
3473                 }
3474         }
3475         $xml .= "        </statcats>\n";
3476
3477     unless ($args->{no_volume}) {
3478         if (ref($self->obj->call_number)) {
3479             $xml .= OpenILS::Application::SuperCat::unAPI
3480                         ->new( $self->obj->call_number )
3481                         ->as_xml({ %$args, no_copies=>1 });
3482         } else {
3483             $xml .= "    <volume/>\n";
3484         }
3485     }
3486
3487     $xml .= "      </serial_unit>\n";
3488
3489     return $xml;
3490 }
3491
3492 package OpenILS::Application::SuperCat::unAPI::scap;
3493 use base qw/OpenILS::Application::SuperCat::unAPI/;
3494
3495 sub as_xml {
3496     my $self = shift;
3497     my $args = shift;
3498
3499     my $xml = '      <caption_and_pattern xmlns="http://open-ils.org/spec/holdings/v1" '.
3500         'id="tag:open-ils.org:serial-caption_and_pattern/' . $self->obj->id . '" ';
3501
3502     $xml .= $_ . '="' . $self->escape( $self->obj->$_  ) . '" ' for (qw/
3503         create_date type active pattern_code enum_1 enum_2 enum_3 enum_4
3504                 enum_5 enum_6 chron_1 chron_2 chron_3 chron_4 chron_5 start_date end_date
3505     /);
3506     $xml .= ">\n";
3507         $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_captions_and_patterns=>1 }) if (!$args->{no_subscription});
3508     $xml .= "      </caption_and_pattern>\n";
3509
3510     return $xml;
3511 }
3512
3513 package OpenILS::Application::SuperCat::unAPI::siss;
3514 use base qw/OpenILS::Application::SuperCat::unAPI/;
3515
3516 sub as_xml {
3517     my $self = shift;
3518     my $args = shift;
3519
3520     my $xml = '      <issuance xmlns="http://open-ils.org/spec/holdings/v1" '.
3521         'id="tag:open-ils.org:serial-issuance/' . $self->obj->id . '" ';
3522
3523     $xml .= $_ . '="' . $self->escape( $self->obj->$_  ) . '" '
3524                 for (qw/create_date edit_date label date_published holding_code holding_type holding_link_id/);
3525
3526     $xml .= ">\n";
3527
3528     if (!$args->{no_items}) {
3529         if (ref($self->obj->items) && @{ $self->obj->items }) {
3530             $xml .= "      <items>\n" . join(
3531                 '',
3532                 map {
3533                     OpenILS::Application::SuperCat::unAPI
3534                         ->new( $_ )
3535                         ->as_xml({ %$args, no_stream=>1 })
3536                 } @{ $self->obj->items }
3537             ) . "      </items>\n";
3538
3539         } else {
3540             $xml .= "      <items/>\n";
3541         }
3542     }
3543
3544         $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_issuances=>1 }) if (!$args->{no_subscription});
3545     $xml .= "      </issuance>\n";
3546
3547     return $xml;
3548 }
3549
3550 package OpenILS::Application::SuperCat::unAPI::acp;
3551 use base qw/OpenILS::Application::SuperCat::unAPI/;
3552
3553 sub as_xml {
3554     my $self = shift;
3555     my $args = shift;
3556
3557     my $xml = '      <copy xmlns="http://open-ils.org/spec/holdings/v1" '.
3558         'id="tag:open-ils.org:asset-copy/' . $self->obj->id . '" ';
3559
3560     $xml .= $_ . '="' . $self->escape( $self->obj->$_  ) . '" ' for (qw/
3561         create_date edit_date copy_number circulate deposit ref holdable deleted
3562         deposit_amount price barcode circ_modifier circ_as_type opac_visible
3563     /);
3564
3565     $xml .= ">\n";
3566
3567     $xml .= '        <status ident="' . $self->obj->status->id . '" opac_visible="' . $self->obj->status->opac_visible . '">' . $self->escape( $self->obj->status->name  ) . "</status>\n";
3568     $xml .= '        <location ident="' . $self->obj->location->id . '" opac_visible="'.$self->obj->location->opac_visible.'">' . $self->escape( $self->obj->location->name  ) . "</location>\n";
3569     $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";
3570
3571     $xml .= '        <circ_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3572     $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->circ_lib->id . '" ';
3573     $xml .= 'shortname="'.$self->escape( $self->obj->circ_lib->shortname ) .'" ';
3574     $xml .= 'name="'.$self->escape( $self->obj->circ_lib->name ) .'" opac_visible="'.$self->obj->circ_lib->opac_visible.'"/>';
3575     $xml .= "\n";
3576
3577         $xml .= "        <monograph_parts>\n";
3578         if (ref($self->obj->parts) && $self->obj->parts) {
3579                 for my $part ( @{$self->obj->parts} ) {
3580                         $xml .= sprintf('        <monograph_part record="%s" sortkey="%s">%s</monograph_part>',$part->record, $self->escape($part->label_sortkey), $self->escape($part->label));
3581                         $xml .= "\n";
3582                 }
3583         }
3584
3585         $xml .= "        </monograph_parts>\n";
3586         $xml .= "        <copy_notes>\n";
3587         if (ref($self->obj->notes) && $self->obj->notes) {
3588                 for my $note ( @{$self->obj->notes} ) {
3589                         next unless ( $note->pub eq 't' );
3590                         $xml .= sprintf('        <copy_note date="%s" title="%s">%s</copy_note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3591                         $xml .= "\n";
3592                 }
3593         }
3594
3595         $xml .= "        </copy_notes>\n";
3596     $xml .= "        <statcats>\n";
3597
3598         if (ref($self->obj->stat_cat_entries) && $self->obj->stat_cat_entries) {
3599                 for my $sce ( @{$self->obj->stat_cat_entries} ) {
3600                         next unless ( $sce->stat_cat->opac_visible eq 't' );
3601                         $xml .= sprintf('          <statcat name="%s">%s</statcat>',$self->escape($sce->stat_cat->name) ,$self->escape($sce->value));
3602                         $xml .= "\n";
3603                 }
3604         }
3605         $xml .= "        </statcats>\n";
3606
3607     unless ($args->{no_volume}) {
3608         if (ref($self->obj->call_number)) {
3609             $xml .= OpenILS::Application::SuperCat::unAPI
3610                         ->new( $self->obj->call_number )
3611                         ->as_xml({ %$args, no_copies=>1 });
3612         } else {
3613             $xml .= "    <volume/>\n";
3614         }
3615     }
3616
3617     $xml .= "      </copy>\n";
3618
3619     return $xml;
3620 }
3621
3622
3623 1;
3624 # vim: et:ts=4:sw=4