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