]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/SuperCat.pm
use a function to wrap up escaping of solidus and casting to bytea, propogate to...
[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 extention 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 => "upper", value => ["upper", $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, upper(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 => "upper", value => ["upper", $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), upper(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 => "upper", value => ["upper", $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, upper(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 => "upper", value => ["upper", $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), upper(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 retrieve, 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 retrieve, 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 retrieve, 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 retrieve, 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 retrieve, 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 retrieve, 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 retrieve, 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 retrieve, 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 retrieve, 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
924 sub authority_tag_sf_browse {
925         my $self = shift;
926         my $client = shift;
927
928         my $tag = shift;
929         my $subfield = shift;
930         my $value = shift;
931         my $page_size = shift || 9;
932         my $page = shift || 0;
933
934         my ($before_limit,$after_limit) = (0,0);
935         my ($before_offset,$after_offset) = (0,0);
936
937         if (!$page) {
938                 $before_limit = $after_limit = int($page_size / 2);
939                 $after_limit += 1 if ($page_size % 2);
940         } else {
941                 $before_offset = $after_offset = int($page_size / 2);
942                 $before_offset += 1 if ($page_size % 2);
943                 $before_limit = $after_limit = $page_size;
944         }
945
946         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
947
948         my @list = ();
949
950         if ($page <= 0) {
951                 my $before = $_storage->request(
952                         "open-ils.cstore.json_query.atomic",
953                         { select        => { afr => [qw/record value/] },
954                           from          => { 'are', 'afr' },
955                           where         => {
956                                 '+afr' => { tag => $tag, subfield => $subfield, value => { '<' => lc($value) } },
957                                 '+are' => { 'deleted' => 'f' }
958                           },
959                           order_by      => { afr => { value => 'desc' } },
960                           limit         => $before_limit,
961                           offset        => abs($page) * $page_size - $before_offset,
962                         }
963                 )->gather(1);
964                 push @list, map { $_->{record} } reverse(@$before);
965         }
966
967         if ($page >= 0) {
968                 my $after = $_storage->request(
969                         "open-ils.cstore.json_query.atomic",
970                         { select        => { afr => [qw/record value/] },
971                           from          => { 'are', 'afr' },
972                           where         => {
973                                 '+afr' => { tag => $tag, subfield => $subfield, value => { '>=' => lc($value) } },
974                                 '+are' => { 'deleted' => 'f' }
975                           },
976                           order_by      => { afr => { value => 'asc' } },
977                           limit         => $after_limit,
978                           offset        => abs($page) * $page_size - $after_offset,
979                         }
980                 )->gather(1);
981                 push @list, map { $_->{record} } @$after;
982         }
983
984         return \@list;
985 }
986 __PACKAGE__->register_method(
987         method    => 'authority_tag_sf_browse',
988         api_name  => 'open-ils.supercat.authority.tag.browse',
989         api_level => 1,
990         argc      => 1,
991         signature =>
992                 { desc     => <<"                 DESC",
993 Returns a list of the requested authority record ids held
994                   DESC
995                   params   =>
996                         [
997                                 { name => 'tag',
998                                   desc => 'The target Authority MARC tag',
999                                   type => 'string' },
1000                                 { name => 'subfield',
1001                                   desc => 'The target Authority MARC subfield',
1002                                   type => 'string' },
1003                                 { name => 'value',
1004                                   desc => 'The target string',
1005                                   type => 'string' },
1006                                 { name => 'page_size',
1007                                   desc => 'Count of call numbers to retrieve, default is 9',
1008                                   type => 'number' },
1009                                 { name => 'page',
1010                                   desc => 'The page of call numbers to retrieve, calculated based on page_size.  Can be positive, negative or 0.',
1011                                   type => 'number' },
1012                         ],
1013                   'return' =>
1014                         { desc => 'Authority Record IDs that are near the target string',
1015                           type => 'array' }
1016                 }
1017 );
1018
1019 sub general_startwith {
1020         my $self = shift;
1021         my $client = shift;
1022     return tag_sf_startwith($self, $client, $self->{tag}, $self->{subfield}, @_);
1023 }
1024 __PACKAGE__->register_method(
1025         method    => 'general_startwith',
1026         api_name  => 'open-ils.supercat.title.startwith',
1027         tag       => 'tnf', subfield => 'a',
1028         api_level => 1,
1029         argc      => 1,
1030         signature =>
1031                 { desc     => "Returns a list of the requested org-scoped record ids held",
1032                   params   =>
1033                         [ { name => 'value', desc => 'The target title', type => 'string' },
1034                           { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1035                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1036                           { name => 'page', desc => 'The page of records retrieve, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' },
1037                           { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1038                           { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1039                   'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1040                 }
1041 );
1042 __PACKAGE__->register_method(
1043         method    => 'general_startwith',
1044         api_name  => 'open-ils.supercat.author.startwith',
1045         tag       => [qw/100 110 111/], subfield => 'a',
1046         api_level => 1,
1047         argc      => 1,
1048         signature =>
1049                 { desc     => "Returns a list of the requested org-scoped record ids held",
1050                   params   =>
1051                         [ { name => 'value', desc => 'The target author', type => 'string' },
1052                           { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1053                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1054                           { name => 'page', desc => 'The page of records retrieve, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' },
1055                           { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1056                           { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1057                   'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1058                 }
1059 );
1060 __PACKAGE__->register_method(
1061         method    => 'general_startwith',
1062         api_name  => 'open-ils.supercat.subject.startwith',
1063         tag       => [qw/600 610 611 630 648 650 651 653 655 656 662 690 691 696 697 698 699/], subfield => 'a',
1064         api_level => 1,
1065         argc      => 1,
1066         signature =>
1067                 { desc     => "Returns a list of the requested org-scoped record ids held",
1068                   params   =>
1069                         [ { name => 'value', desc => 'The target subject', type => 'string' },
1070                           { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1071                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1072                           { name => 'page', desc => 'The page of records retrieve, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' },
1073                           { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1074                           { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1075                   'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1076                 }
1077 );
1078 __PACKAGE__->register_method(
1079         method    => 'general_startwith',
1080         api_name  => 'open-ils.supercat.topic.startwith',
1081         tag       => [qw/650 690/], subfield => 'a',
1082         api_level => 1,
1083         argc      => 1,
1084         signature =>
1085                 { desc     => "Returns a list of the requested org-scoped record ids held",
1086                   params   =>
1087                         [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1088                           { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1089                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1090                           { name => 'page', desc => 'The page of records retrieve, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' },
1091                           { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1092                           { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1093                   'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1094                 }
1095 );
1096 __PACKAGE__->register_method(
1097         method    => 'general_startwith',
1098         api_name  => 'open-ils.supercat.series.startwith',
1099         tag       => [qw/440 490 800 810 811 830/], subfield => 'a',
1100         api_level => 1,
1101         argc      => 1,
1102         signature =>
1103                 { desc     => "Returns a list of the requested org-scoped record ids held",
1104                   params   =>
1105                         [ { name => 'value', desc => 'The target series', type => 'string' },
1106                           { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
1107                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1108                           { name => 'page', desc => 'The page of records retrieve, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' },
1109                           { name => 'statuses', desc => 'Array of statuses to filter copies by, optional and can be undef.', type => 'array' },
1110                           { name => 'locations', desc => 'Array of copy locations to filter copies by, optional and can be undef.', type => 'array' }, ],
1111                   'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
1112                 }
1113 );
1114
1115
1116 sub tag_sf_startwith {
1117         my $self = shift;
1118         my $client = shift;
1119
1120         my $tag = shift;
1121         my $subfield = shift;
1122         my $value = shift;
1123         my $ou = shift;
1124         my $limit = shift || 10;
1125         my $page = shift || 0;
1126         my $statuses = shift || [];
1127         my $copy_locations = shift || [];
1128
1129         my $offset = $limit * abs($page);
1130         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1131
1132         my @ou_ids;
1133         if ($ou && $ou ne '-') {
1134                 my $orgs = $_storage->request(
1135                         "open-ils.cstore.direct.actor.org_unit.search",
1136                         { shortname => $ou },
1137                         { flesh         => 100,
1138                           flesh_fields  => { aou        => [qw/children/] }
1139                         }
1140                 )->gather(1);
1141                 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
1142         }
1143
1144         $logger->debug("Searching for records at orgs [".join(',',@ou_ids)."], based on $ou");
1145
1146         my @list = ();
1147
1148         if ($page < 0) {
1149                 my $before = $_storage->request(
1150                         "open-ils.cstore.json_query.atomic",
1151                         { select        => { mfr => [qw/record value/] },
1152                           from          => 'mfr',
1153                           where         =>
1154                                 { '+mfr'        =>
1155                                         { tag   => $tag,
1156                                           subfield => $subfield,
1157                                           value => { '<' => lc($value) }
1158                                         },
1159                   '-or' => [
1160                                 { '-exists'     =>
1161                                         { select=> { acp => [ 'id' ] },
1162                                           from  => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
1163                                               where     =>
1164                                                 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
1165                                                   '+acp' =>
1166                                                                 { deleted => 'f',
1167                                                                   ((@ou_ids)          ? ( circ_lib => \@ou_ids)        : ()),
1168                                                                   ((@$statuses)       ? ( status   => $statuses)       : ()),
1169                                                                   ((@$copy_locations) ? ( location => $copy_locations) : ())
1170                                                                 }
1171                                                 },
1172                                           limit => 1
1173                                         }
1174                     },
1175                     { '-exists' =>
1176                                         { select=> { auri => [ 'id' ] },
1177                                           from  => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
1178                                           where =>
1179                                                 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
1180                                                   '+auri' => { active => 't' }
1181                                                 },
1182                                           limit => 1
1183                                         }
1184                     }
1185                   ]
1186                                 }, 
1187                           order_by      => { mfr => { value => 'desc' } },
1188                           limit         => $limit,
1189                           offset        => $offset
1190                         }
1191                 )->gather(1);
1192                 push @list, map { $_->{record} } reverse(@$before);
1193         }
1194
1195         if ($page >= 0) {
1196                 my $after = $_storage->request(
1197                         "open-ils.cstore.json_query.atomic",
1198                         { select        => { mfr => [qw/record value/] },
1199                           from          => 'mfr',
1200                           where         =>
1201                                 { '+mfr'        =>
1202                                         { tag   => $tag,
1203                                           subfield => $subfield,
1204                                           value => { '>=' => lc($value) }
1205                                         },
1206                                   '-or' => [
1207                     { '-exists' =>
1208                                         { select=> { acp => [ 'id' ] },
1209                                           from  => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
1210                                           where =>
1211                                                 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
1212                                                   '+acp' =>
1213                                                                 { deleted => 'f',
1214                                                                   ((@ou_ids)          ? ( circ_lib => \@ou_ids)        : ()),
1215                                                                   ((@$statuses)       ? ( status   => $statuses)       : ()),
1216                                                                   ((@$copy_locations) ? ( location => $copy_locations) : ())
1217                                                                 }
1218                                                 },
1219                                           limit => 1
1220                                         }
1221                     },
1222                     { '-exists' =>
1223                                         { select=> { auri => [ 'id' ] },
1224                                           from  => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
1225                                           where =>
1226                                                 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
1227                                                   '+auri' => { active => 't' }
1228                                                 },
1229                                           limit => 1
1230                                         },
1231                     }
1232                   ]
1233                                 }, 
1234                           order_by      => { mfr => { value => 'asc' } },
1235                           limit         => $limit,
1236                           offset        => $offset
1237                         }
1238                 )->gather(1);
1239                 push @list, map { $_->{record} } @$after;
1240         }
1241
1242         return \@list;
1243 }
1244 __PACKAGE__->register_method(
1245         method    => 'tag_sf_startwith',
1246         api_name  => 'open-ils.supercat.tag.startwith',
1247         api_level => 1,
1248         argc      => 1,
1249         signature =>
1250                 { desc     => <<"                 DESC",
1251 Returns a list of the requested org-scoped record ids held
1252                   DESC
1253                   params   =>
1254                         [
1255                                 { name => 'tag',
1256                                   desc => 'The target MARC tag',
1257                                   type => 'string' },
1258                                 { name => 'subfield',
1259                                   desc => 'The target MARC subfield',
1260                                   type => 'string' },
1261                                 { name => 'value',
1262                                   desc => 'The target string',
1263                                   type => 'string' },
1264                                 { name => 'org_unit',
1265                                   desc => 'The org unit shortname (or "-" or undef for global) to browse',
1266                                   type => 'string' },
1267                                 { name => 'page_size',
1268                                   desc => 'Count of call numbers to retrieve, default is 9',
1269                                   type => 'number' },
1270                                 { name => 'page',
1271                                   desc => 'The page of call numbers to retrieve, calculated based on page_size.  Can be positive, negative or 0.',
1272                                   type => 'number' },
1273                                 { name => 'statuses',
1274                                   desc => 'Array of statuses to filter copies by, optional and can be undef.',
1275                                   type => 'array' },
1276                                 { name => 'locations',
1277                                   desc => 'Array of copy locations to filter copies by, optional and can be undef.',
1278                                   type => 'array' },
1279                         ],
1280                   'return' =>
1281                         { desc => 'Record IDs that have copies at the relevant org units',
1282                           type => 'array' }
1283                 }
1284 );
1285
1286 sub general_authority_startwith {
1287         my $self = shift;
1288         my $client = shift;
1289     return authority_tag_sf_startwith($self, $client, $self->{tag}, $self->{subfield}, @_);
1290 }
1291 __PACKAGE__->register_method(
1292         method    => 'general_authority_startwith',
1293         api_name  => 'open-ils.supercat.authority.title.startwith',
1294         tag       => '130', subfield => 'a',
1295         api_level => 1,
1296         argc      => 1,
1297         signature =>
1298                 { desc     => "Returns a list of the requested authority record ids held",
1299                   params   =>
1300                         [ { name => 'value', desc => 'The target title', type => 'string' },
1301                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1302                           { name => 'page', desc => 'The page of records retrieve, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
1303                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1304                 }
1305 );
1306 __PACKAGE__->register_method(
1307         method    => 'general_authority_startwith',
1308         api_name  => 'open-ils.supercat.authority.author.startwith',
1309         tag       => [qw/100 110 111/], subfield => 'a',
1310         api_level => 1,
1311         argc      => 1,
1312         signature =>
1313                 { desc     => "Returns a list of the requested authority record ids held",
1314                   params   =>
1315                         [ { name => 'value', desc => 'The target author', type => 'string' },
1316                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1317                           { name => 'page', desc => 'The page of records retrieve, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
1318                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1319                 }
1320 );
1321 __PACKAGE__->register_method(
1322         method    => 'general_authority_startwith',
1323         api_name  => 'open-ils.supercat.authority.subject.startwith',
1324         tag       => [qw/148 150 151 155/], subfield => 'a',
1325         api_level => 1,
1326         argc      => 1,
1327         signature =>
1328                 { desc     => "Returns a list of the requested authority record ids held",
1329                   params   =>
1330                         [ { name => 'value', desc => 'The target subject', type => 'string' },
1331                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1332                           { name => 'page', desc => 'The page of records retrieve, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
1333                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1334                 }
1335 );
1336 __PACKAGE__->register_method(
1337         method    => 'general_authority_startwith',
1338         api_name  => 'open-ils.supercat.authority.topic.startwith',
1339         tag       => '150', subfield => 'a',
1340         api_level => 1,
1341         argc      => 1,
1342         signature =>
1343                 { desc     => "Returns a list of the requested authority record ids held",
1344                   params   =>
1345                         [ { name => 'value', desc => 'The target topical subject', type => 'string' },
1346                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
1347                           { name => 'page', desc => 'The page of records retrieve, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
1348                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
1349                 }
1350 );
1351
1352 sub authority_tag_sf_startwith {
1353         my $self = shift;
1354         my $client = shift;
1355
1356         my $tag = shift;
1357         my $subfield = shift;
1358         my $value = shift;
1359         my $limit = shift || 10;
1360         my $page = shift || 0;
1361
1362         my $offset = $limit * abs($page);
1363         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1364
1365         my @list = ();
1366
1367         if ($page < 0) {
1368                 my $before = $_storage->request(
1369                         "open-ils.cstore.json_query.atomic",
1370                         { select        => { afr => [qw/record value/] },
1371                           from          => { 'afr', 'are' },
1372                           where         => {
1373                                 '+afr' => { tag => $tag, subfield => $subfield, value => { '<' => lc($value) } },
1374                                 '+are' => { deleted => 'f' }
1375                           },
1376                           order_by      => { afr => { value => 'desc' } },
1377                           limit         => $limit,
1378                           offset        => $offset
1379                         }
1380                 )->gather(1);
1381                 push @list, map { $_->{record} } reverse(@$before);
1382         }
1383
1384         if ($page >= 0) {
1385                 my $after = $_storage->request(
1386                         "open-ils.cstore.json_query.atomic",
1387                         { select        => { afr => [qw/record value/] },
1388                           from          => { 'afr', 'are' },
1389                           where         => {
1390                                 '+afr' => { tag => $tag, subfield => $subfield, value => { '>=' => lc($value) } },
1391                                 '+are' => { deleted => 'f' }
1392                           },
1393                           order_by      => { afr => { value => 'asc' } },
1394                           limit         => $limit,
1395                           offset        => $offset
1396                         }
1397                 )->gather(1);
1398                 push @list, map { $_->{record} } @$after;
1399         }
1400
1401         return \@list;
1402 }
1403 __PACKAGE__->register_method(
1404         method    => 'authority_tag_sf_startwith',
1405         api_name  => 'open-ils.supercat.authority.tag.startwith',
1406         api_level => 1,
1407         argc      => 1,
1408         signature =>
1409                 { desc     => <<"                 DESC",
1410 Returns a list of the requested authority record ids held
1411                   DESC
1412                   params   =>
1413                         [
1414                                 { name => 'tag',
1415                                   desc => 'The target Authority MARC tag',
1416                                   type => 'string' },
1417                                 { name => 'subfield',
1418                                   desc => 'The target Authority MARC subfield',
1419                                   type => 'string' },
1420                                 { name => 'value',
1421                                   desc => 'The target string',
1422                                   type => 'string' },
1423                                 { name => 'page_size',
1424                                   desc => 'Count of call numbers to retrieve, default is 9',
1425                                   type => 'number' },
1426                                 { name => 'page',
1427                                   desc => 'The page of call numbers to retrieve, calculated based on page_size.  Can be positive, negative or 0.',
1428                                   type => 'number' },
1429                         ],
1430                   'return' =>
1431                         { desc => 'Authority Record IDs that are near the target string',
1432                           type => 'array' }
1433                 }
1434 );
1435
1436
1437 sub holding_data_formats {
1438     return [{
1439         marcxml => {
1440             namespace_uri         => 'http://www.loc.gov/MARC21/slim',
1441                         docs              => 'http://www.loc.gov/marcxml/',
1442                         schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
1443                 }
1444         }];
1445 }
1446 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.acn.formats', api_level => 1 );
1447 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.acp.formats', api_level => 1 );
1448 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.auri.formats', api_level => 1 );
1449
1450
1451 __PACKAGE__->register_method(
1452         method    => 'retrieve_uri',
1453         api_name  => 'open-ils.supercat.auri.marcxml.retrieve',
1454         api_level => 1,
1455         argc      => 1,
1456         signature =>
1457                 { desc     => <<"                 DESC",
1458 Returns a fleshed call number object
1459                   DESC
1460                   params   =>
1461                         [
1462                                 { name => 'uri_id',
1463                                   desc => 'An OpenILS asset::uri id',
1464                                   type => 'number' },
1465                         ],
1466                   'return' =>
1467                         { desc => 'fleshed uri',
1468                           type => 'object' }
1469                 }
1470 );
1471 sub retrieve_uri {
1472         my $self = shift;
1473         my $client = shift;
1474         my $cpid = shift;
1475         my $args = shift || {};
1476
1477     return OpenILS::Application::SuperCat::unAPI
1478         ->new(OpenSRF::AppSession
1479             ->create( 'open-ils.cstore' )
1480             ->request(
1481                 "open-ils.cstore.direct.asset.uri.retrieve",
1482                     $cpid,
1483                     { flesh             => 10,
1484                           flesh_fields  => {
1485                                                 auri    => [qw/call_number_maps/],
1486                                                 auricnm => [qw/call_number/],
1487                                                 acn         => [qw/owning_lib record/],
1488                                 }
1489                     })
1490             ->gather(1))
1491         ->as_xml($args);
1492 }
1493
1494 __PACKAGE__->register_method(
1495         method    => 'retrieve_copy',
1496         api_name  => 'open-ils.supercat.acp.marcxml.retrieve',
1497         api_level => 1,
1498         argc      => 1,
1499         signature =>
1500                 { desc     => <<"                 DESC",
1501 Returns a fleshed call number object
1502                   DESC
1503                   params   =>
1504                         [
1505                                 { name => 'cn_id',
1506                                   desc => 'An OpenILS asset::copy id',
1507                                   type => 'number' },
1508                         ],
1509                   'return' =>
1510                         { desc => 'fleshed copy',
1511                           type => 'object' }
1512                 }
1513 );
1514 sub retrieve_copy {
1515         my $self = shift;
1516         my $client = shift;
1517         my $cpid = shift;
1518         my $args = shift || {};
1519
1520     return OpenILS::Application::SuperCat::unAPI
1521         ->new(OpenSRF::AppSession
1522             ->create( 'open-ils.cstore' )
1523             ->request(
1524                 "open-ils.cstore.direct.asset.copy.retrieve",
1525                     $cpid,
1526                     { flesh             => 2,
1527                           flesh_fields  => {
1528                                                 acn     => [qw/owning_lib record/],
1529                                                 acp     => [qw/call_number location status circ_lib stat_cat_entries notes/],
1530                                 }
1531                     })
1532             ->gather(1))
1533         ->as_xml($args);
1534 }
1535
1536 __PACKAGE__->register_method(
1537         method    => 'retrieve_callnumber',
1538         api_name  => 'open-ils.supercat.acn.marcxml.retrieve',
1539         api_level => 1,
1540         argc      => 1,
1541         stream    => 1,
1542         signature =>
1543                 { desc     => <<"                 DESC",
1544 Returns a fleshed call number object
1545                   DESC
1546                   params   =>
1547                         [
1548                                 { name => 'cn_id',
1549                                   desc => 'An OpenILS asset::call_number id',
1550                                   type => 'number' },
1551                         ],
1552                   'return' =>
1553                         { desc => 'call number with copies',
1554                           type => 'object' }
1555                 }
1556 );
1557 sub retrieve_callnumber {
1558         my $self = shift;
1559         my $client = shift;
1560         my $cnid = shift;
1561         my $args = shift || {};
1562
1563     return OpenILS::Application::SuperCat::unAPI
1564         ->new(OpenSRF::AppSession
1565             ->create( 'open-ils.cstore' )
1566             ->request(
1567                 "open-ils.cstore.direct.asset.call_number.retrieve",
1568                     $cnid,
1569                     { flesh             => 5,
1570                           flesh_fields  => {
1571                                                 acn     => [qw/owning_lib record copies uri_maps/],
1572                                                 auricnm => [qw/uri/],
1573                                                 acp     => [qw/location status circ_lib stat_cat_entries notes/],
1574                                 }
1575                     })
1576             ->gather(1))
1577         ->as_xml($args);
1578
1579 }
1580
1581 __PACKAGE__->register_method(
1582         method    => 'basic_record_holdings',
1583         api_name  => 'open-ils.supercat.record.basic_holdings.retrieve',
1584         api_level => 1,
1585         argc      => 1,
1586         stream    => 1,
1587         signature =>
1588                 { desc     => <<"                 DESC",
1589 Returns a basic hash representation of the requested bibliographic record's holdings
1590                   DESC
1591                   params   =>
1592                         [
1593                                 { name => 'bibId',
1594                                   desc => 'An OpenILS biblio::record_entry id',
1595                                   type => 'number' },
1596                         ],
1597                   'return' =>
1598                         { desc => 'Hash of bib record holdings hierarchy (call numbers and copies)',
1599                           type => 'string' }
1600                 }
1601 );
1602 sub basic_record_holdings {
1603         my $self = shift;
1604         my $client = shift;
1605         my $bib = shift;
1606         my $ou = shift;
1607
1608         #  holdings hold an array of call numbers, which hold an array of copies
1609         #  holdings => [ label: { library, [ copies: { barcode, location, status, circ_lib } ] } ]
1610         my %holdings;
1611
1612         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1613
1614         my $tree = $_storage->request(
1615                 "open-ils.cstore.direct.biblio.record_entry.retrieve",
1616                 $bib,
1617                 { flesh         => 5,
1618                   flesh_fields  => {
1619                                         bre     => [qw/call_numbers/],
1620                                         acn     => [qw/copies owning_lib/],
1621                                         acp     => [qw/location status circ_lib/],
1622                                 }
1623                 }
1624         )->gather(1);
1625
1626         my $o_search = { shortname => uc($ou) };
1627         if (!$ou || $ou eq '-') {
1628                 $o_search = { parent_ou => undef };
1629         }
1630
1631         my $orgs = $_storage->request(
1632                 "open-ils.cstore.direct.actor.org_unit.search",
1633                 $o_search,
1634                 { flesh         => 100,
1635                   flesh_fields  => { aou        => [qw/children/] }
1636                 }
1637         )->gather(1);
1638
1639         my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
1640
1641         $logger->debug("Searching for holdings at orgs [".join(',',@ou_ids)."], based on $ou");
1642
1643         for my $cn (@{$tree->call_numbers}) {
1644         next unless ( $cn->deleted eq 'f' || $cn->deleted == 0 );
1645
1646                 my $found = 0;
1647                 for my $c (@{$cn->copies}) {
1648                         next unless grep {$c->circ_lib->id == $_} @ou_ids;
1649                         next unless ( $c->deleted eq 'f' || $c->deleted == 0 );
1650                         $found = 1;
1651                         last;
1652                 }
1653                 next unless $found;
1654
1655                 $holdings{$cn->label}{'owning_lib'} = $cn->owning_lib->shortname;
1656
1657                 for my $cp (@{$cn->copies}) {
1658
1659                         next unless grep { $cp->circ_lib->id == $_ } @ou_ids;
1660                         next unless ( $cp->deleted eq 'f' || $cp->deleted == 0 );
1661
1662                         push @{$holdings{$cn->label}{'copies'}}, {
1663                 barcode => $cp->barcode,
1664                 status => $cp->status->name,
1665                 location => $cp->location->name,
1666                 circlib => $cp->circ_lib->shortname
1667             };
1668
1669                 }
1670         }
1671
1672         return \%holdings;
1673 }
1674
1675 #__PACKAGE__->register_method(
1676 #       method    => 'new_record_holdings',
1677 #       api_name  => 'open-ils.supercat.record.holdings_xml.retrieve',
1678 #       api_level => 1,
1679 #       argc      => 1,
1680 #       stream    => 1,
1681 #       signature =>
1682 #               { desc     => <<"                 DESC",
1683 #Returns the XML representation of the requested bibliographic record's holdings
1684 #                 DESC
1685 #                 params   =>
1686 #                       [
1687 #                               { name => 'bibId',
1688 #                                 desc => 'An OpenILS biblio::record_entry id',
1689 #                                 type => 'number' },
1690 #                       ],
1691 #                 'return' =>
1692 #                       { desc => 'Stream of bib record holdings hierarchy in XML',
1693 #                         type => 'string' }
1694 #               }
1695 #);
1696 #
1697
1698 sub new_record_holdings {
1699         my $self = shift;
1700         my $client = shift;
1701         my $bib = shift;
1702         my $ou = shift;
1703         my $depth = shift;
1704         my $flesh = shift;
1705         my $paging = shift;
1706
1707     $paging = [-1,0] if (!$paging or !ref($paging) or @$paging == 0);
1708     my $limit = $$paging[0];
1709     my $offset = $$paging[1] || 0;
1710
1711         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1712
1713         my $o_search = { shortname => uc($ou) };
1714         if (!$ou || $ou eq '-') {
1715                 $o_search = { parent_ou => undef };
1716         }
1717
1718     my $one_org = $_storage->request(
1719         "open-ils.cstore.direct.actor.org_unit.search",
1720         $o_search
1721     )->gather(1);
1722
1723     my $orgs = $_storage->request(
1724         'open-ils.cstore.json_query.atomic',
1725         { from => [ 'actor.org_unit_descendants', defined($depth) ? ( $one_org->id, $depth ) :  ( $one_org->id ) ] }
1726     )->gather(1);
1727
1728
1729         my @ou_ids = map { $_->{id} } @$orgs;
1730
1731         $logger->info("Searching for holdings at orgs [".join(',',@ou_ids)."], based on $ou");
1732
1733     my %subselect = ( '-or' => [
1734         { owning_lib => \@ou_ids },
1735         { '-exists'  =>
1736             { from  => 'acp',
1737               where => {
1738                 call_number => { '=' => {'+acn'=>'id'} },
1739                 deleted => 'f',
1740                 circ_lib => \@ou_ids
1741               }
1742             }
1743         }
1744     ]);
1745
1746     if ($flesh and $flesh eq 'uris') {
1747         %subselect = (
1748             owning_lib => \@ou_ids,
1749             '-exists'  => {
1750                 from  => { auricnm => 'auri' },
1751                 where => {
1752                     call_number => { '=' => {'+acn'=>'id'} },
1753                     '+auri' => { active => 't' }
1754                 }
1755             }
1756         );
1757     }
1758
1759
1760         my $cns = $_storage->request(
1761                 "open-ils.cstore.direct.asset.call_number.search.atomic",
1762                 { record  => $bib,
1763           deleted => 'f',
1764           %subselect
1765         },
1766                 { flesh         => 5,
1767                   flesh_fields  => {
1768                                         acn     => [qw/copies owning_lib uri_maps/],
1769                                         auricnm => [qw/uri/],
1770                                         acp     => [qw/circ_lib location status stat_cat_entries notes/],
1771                                         asce    => [qw/stat_cat/],
1772                                 },
1773           ( $limit > -1 ? ( limit  => $limit  ) : () ),
1774           ( $offset     ? ( offset => $offset ) : () ),
1775           order_by  => { acn => { label_sortkey => {} } }
1776                 }
1777         )->gather(1);
1778
1779         my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
1780         $year += 1900;
1781         $month += 1;
1782
1783         $client->respond("<holdings xmlns='http://open-ils.org/spec/holdings/v1'><volumes>\n");
1784     
1785         for my $cn (@$cns) {
1786                 next unless (@{$cn->copies} > 0 or (ref($cn->uri_maps) and @{$cn->uri_maps}));
1787
1788                 # We don't want O:A:S:unAPI::acn to return the record, we've got that already
1789                 # In the context of BibTemplate, copies aren't necessary because we pull those
1790                 # in a separate call
1791         $client->respond(
1792             OpenILS::Application::SuperCat::unAPI::acn
1793                 ->new( $cn )
1794                 ->as_xml( {no_record => 1, no_copies => ($flesh ? 0 : 1)} )
1795         );
1796         }
1797
1798         $client->respond("</volumes><subscriptions>\n");
1799
1800         $logger->info("Searching for serial holdings at orgs [".join(',',@ou_ids)."], based on $ou");
1801
1802     %subselect = ( '-or' => [
1803         { owning_lib => \@ou_ids },
1804         { '-exists'  =>
1805             { from  => 'sdist',
1806               where => { holding_lib => \@ou_ids }
1807             }
1808         }
1809     ]);
1810
1811         my $ssubs = $_storage->request(
1812                 "open-ils.cstore.direct.serial.subscription.search.atomic",
1813                 { record_entry  => $bib,
1814           %subselect
1815         },
1816                 { flesh         => 5,
1817                   flesh_fields  => {
1818                                         ssub    => [qw/sdist siss sercap/],
1819                                         sdist   => [qw/bib_summaries sup_summaries index_summaries streams/],
1820                                         sstr    => [qw/items/],
1821                                         sitem   => [qw/notes unit/],
1822                                 },
1823           ( $limit > -1 ? ( limit  => $limit  ) : () ),
1824           ( $offset     ? ( offset => $offset ) : () ),
1825           order_by  => {
1826                         ssub => {
1827                                 start_date => {},
1828                                 owning_lib => {},
1829                                 id => {}
1830                         },
1831                         sdist => {
1832                                 label => {},
1833                                 owning_lib => {},
1834                         },
1835                         sunit => {
1836                                 date_expected => {},
1837                         }
1838                   }
1839                 }
1840         )->gather(1);
1841
1842
1843         for my $ssub (@$ssubs) {
1844                 next unless (@{$ssub->distributions} or @{$ssub->issuances} or @{$ssub->captions_and_patterns});
1845
1846                 # We don't want O:A:S:unAPI::ssub to return the record, we've got that already
1847                 # In the context of BibTemplate, copies aren't necessary because we pull those
1848                 # in a separate call
1849         $client->respond(
1850             OpenILS::Application::SuperCat::unAPI::ssub
1851                 ->new( $ssub )
1852                 ->as_xml( {no_record => 1, no_items => ($flesh ? 0 : 1)} )
1853         );
1854         }
1855
1856
1857         return "</subscriptions></holdings>\n";
1858 }
1859 __PACKAGE__->register_method(
1860         method    => 'new_record_holdings',
1861         api_name  => 'open-ils.supercat.record.holdings_xml.retrieve',
1862         api_level => 1,
1863         argc      => 1,
1864         stream    => 1,
1865         signature =>
1866                 { desc     => <<"                 DESC",
1867 Returns the XML representation of the requested bibliographic record's holdings
1868                   DESC
1869                   params   =>
1870                         [
1871                                 { name => 'bibId',
1872                                   desc => 'An OpenILS biblio::record_entry ID',
1873                                   type => 'number' },
1874                                 { name => 'orgUnit',
1875                                   desc => 'An OpenILS actor::org_unit short name that limits the scope of returned holdings',
1876                                   type => 'text' },
1877                                 { name => 'depth',
1878                                   desc => 'An OpenILS actor::org_unit_type depththat limits the scope of returned holdings',
1879                                   type => 'number' },
1880                                 { name => 'hideCopies',
1881                                   desc => 'Flag that prevents the inclusion of copies in the returned holdings',
1882                                   type => 'boolean' },
1883                                 { name => 'paging',
1884                                   desc => 'Arry of limit and offset for holdings paging',
1885                                   type => 'array' },
1886                         ],
1887                   'return' =>
1888                         { desc => 'Stream of bib record holdings hierarchy in XML',
1889                           type => 'string' }
1890                 }
1891 );
1892
1893 sub isbn_holdings {
1894         my $self = shift;
1895         my $client = shift;
1896         my $isbn = shift;
1897
1898         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1899
1900         my $recs = $_storage->request(
1901                         'open-ils.cstore.direct.metabib.full_rec.search.atomic',
1902                         { tag => { like => '02%'}, value => {like => "$isbn\%"}}
1903         )->gather(1);
1904
1905         return undef unless (@$recs);
1906
1907         return ($self->method_lookup( 'open-ils.supercat.record.holdings_xml.retrieve')->run( $recs->[0]->record ))[0];
1908 }
1909 __PACKAGE__->register_method(
1910         method    => 'isbn_holdings',
1911         api_name  => 'open-ils.supercat.isbn.holdings_xml.retrieve',
1912         api_level => 1,
1913         argc      => 1,
1914         signature =>
1915                 { desc     => <<"                 DESC",
1916 Returns the XML representation of the requested bibliographic record's holdings
1917                   DESC
1918                   params   =>
1919                         [
1920                                 { name => 'isbn',
1921                                   desc => 'An isbn',
1922                                   type => 'string' },
1923                         ],
1924                   'return' =>
1925                         { desc => 'The bib record holdings hierarchy in XML',
1926                           type => 'string' }
1927                 }
1928 );
1929
1930 sub escape {
1931         my $self = shift;
1932         my $text = shift;
1933     return '' unless $text;
1934         $text =~ s/&/&amp;/gsom;
1935         $text =~ s/</&lt;/gsom;
1936         $text =~ s/>/&gt;/gsom;
1937         $text =~ s/"/\\"/gsom;
1938         return $text;
1939 }
1940
1941 sub recent_changes {
1942         my $self = shift;
1943         my $client = shift;
1944         my $when = shift || '1-01-01';
1945         my $limit = shift;
1946
1947         my $type = 'biblio';
1948         $type = 'authority' if ($self->api_name =~ /authority/o);
1949
1950         my $axis = 'create_date';
1951         $axis = 'edit_date' if ($self->api_name =~ /edit/o);
1952
1953         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1954
1955         return $_storage->request(
1956                 "open-ils.cstore.direct.$type.record_entry.id_list.atomic",
1957                 { $axis => { ">" => $when }, id => { '>' => 0 } },
1958                 { order_by => { bre => "$axis desc" }, limit => $limit }
1959         )->gather(1);
1960 }
1961
1962 for my $t ( qw/biblio authority/ ) {
1963         for my $a ( qw/import edit/ ) {
1964
1965                 __PACKAGE__->register_method(
1966                         method    => 'recent_changes',
1967                         api_name  => "open-ils.supercat.$t.record.$a.recent",
1968                         api_level => 1,
1969                         argc      => 0,
1970                         signature =>
1971                                 { desc     => "Returns a list of recently ${a}ed $t records",
1972                                   params   =>
1973                                         [
1974                                                 { name => 'when',
1975                                                   desc => "Date to start looking for ${a}ed records",
1976                                                   default => '1-01-01',
1977                                                   type => 'string' },
1978
1979                                                 { name => 'limit',
1980                                                   desc => "Maximum count to retrieve",
1981                                                   type => 'number' },
1982                                         ],
1983                                   'return' =>
1984                                         { desc => "An id list of $t records",
1985                                           type => 'array' }
1986                                 },
1987                 );
1988         }
1989 }
1990
1991
1992 sub retrieve_authority_marcxml {
1993         my $self = shift;
1994         my $client = shift;
1995         my $rid = shift;
1996
1997         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1998
1999         my $record = $_storage->request( 'open-ils.cstore.direct.authority.record_entry.retrieve' => $rid )->gather(1);
2000         return $U->entityize( $record->marc ) if ($record);
2001         return undef;
2002 }
2003
2004 __PACKAGE__->register_method(
2005         method    => 'retrieve_authority_marcxml',
2006         api_name  => 'open-ils.supercat.authority.marcxml.retrieve',
2007         api_level => 1,
2008         argc      => 1,
2009         signature =>
2010                 { desc     => <<"                 DESC",
2011 Returns the MARCXML representation of the requested authority record
2012                   DESC
2013                   params   =>
2014                         [
2015                                 { name => 'authorityId',
2016                                   desc => 'An OpenILS authority::record_entry id',
2017                                   type => 'number' },
2018                         ],
2019                   'return' =>
2020                         { desc => 'The authority record in MARCXML',
2021                           type => 'string' }
2022                 }
2023 );
2024
2025 sub retrieve_record_marcxml {
2026         my $self = shift;
2027         my $client = shift;
2028         my $rid = shift;
2029
2030         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2031
2032         my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rid )->gather(1);
2033         return $U->entityize( $record->marc ) if ($record);
2034         return undef;
2035 }
2036
2037 __PACKAGE__->register_method(
2038         method    => 'retrieve_record_marcxml',
2039         api_name  => 'open-ils.supercat.record.marcxml.retrieve',
2040         api_level => 1,
2041         argc      => 1,
2042         signature =>
2043                 { desc     => <<"                 DESC",
2044 Returns the MARCXML representation of the requested bibliographic record
2045                   DESC
2046                   params   =>
2047                         [
2048                                 { name => 'bibId',
2049                                   desc => 'An OpenILS biblio::record_entry id',
2050                                   type => 'number' },
2051                         ],
2052                   'return' =>
2053                         { desc => 'The bib record in MARCXML',
2054                           type => 'string' }
2055                 }
2056 );
2057
2058 sub retrieve_isbn_marcxml {
2059         my $self = shift;
2060         my $client = shift;
2061         my $isbn = shift;
2062
2063         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2064
2065         my $recs = $_storage->request(
2066                         'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2067                         { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2068         )->gather(1);
2069
2070         return undef unless (@$recs);
2071
2072         my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
2073         return $U->entityize( $record->marc ) if ($record);
2074         return undef;
2075 }
2076
2077 __PACKAGE__->register_method(
2078         method    => 'retrieve_isbn_marcxml',
2079         api_name  => 'open-ils.supercat.isbn.marcxml.retrieve',
2080         api_level => 1,
2081         argc      => 1,
2082         signature =>
2083                 { desc     => <<"                 DESC",
2084 Returns the MARCXML representation of the requested ISBN
2085                   DESC
2086                   params   =>
2087                         [
2088                                 { name => 'ISBN',
2089                                   desc => 'An ... um ... ISBN',
2090                                   type => 'string' },
2091                         ],
2092                   'return' =>
2093                         { desc => 'The bib record in MARCXML',
2094                           type => 'string' }
2095                 }
2096 );
2097
2098 sub retrieve_record_transform {
2099         my $self = shift;
2100         my $client = shift;
2101         my $rid = shift;
2102
2103         (my $transform = $self->api_name) =~ s/^.+record\.([^\.]+)\.retrieve$/$1/o;
2104
2105         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2106         #$_storage->connect;
2107
2108         my $record = $_storage->request(
2109                 'open-ils.cstore.direct.biblio.record_entry.retrieve',
2110                 $rid
2111         )->gather(1);
2112
2113         return undef unless ($record);
2114
2115         return $U->entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $record->marc ) )->toString);
2116 }
2117
2118 sub retrieve_isbn_transform {
2119         my $self = shift;
2120         my $client = shift;
2121         my $isbn = shift;
2122
2123         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2124
2125         my $recs = $_storage->request(
2126                         'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2127                         { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2128         )->gather(1);
2129
2130         return undef unless (@$recs);
2131
2132         (my $transform = $self->api_name) =~ s/^.+isbn\.([^\.]+)\.retrieve$/$1/o;
2133
2134         my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
2135
2136         return undef unless ($record);
2137
2138         return $U->entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $record->marc ) )->toString);
2139 }
2140
2141 sub retrieve_record_objects {
2142         my $self = shift;
2143         my $client = shift;
2144         my $ids = shift;
2145
2146         $ids = [$ids] unless (ref $ids);
2147         $ids = [grep {$_} @$ids];
2148
2149         return [] unless (@$ids);
2150
2151         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2152         return $_storage->request('open-ils.cstore.direct.biblio.record_entry.search.atomic' => { id => [grep {$_} @$ids] })->gather(1);
2153 }
2154 __PACKAGE__->register_method(
2155         method    => 'retrieve_record_objects',
2156         api_name  => 'open-ils.supercat.record.object.retrieve',
2157         api_level => 1,
2158         argc      => 1,
2159         signature =>
2160                 { desc     => <<"                 DESC",
2161 Returns the Fieldmapper object representation of the requested bibliographic records
2162                   DESC
2163                   params   =>
2164                         [
2165                                 { name => 'bibIds',
2166                                   desc => 'OpenILS biblio::record_entry ids',
2167                                   type => 'array' },
2168                         ],
2169                   'return' =>
2170                         { desc => 'The bib records',
2171                           type => 'array' }
2172                 }
2173 );
2174
2175
2176 sub retrieve_isbn_object {
2177         my $self = shift;
2178         my $client = shift;
2179         my $isbn = shift;
2180
2181         return undef unless ($isbn);
2182
2183         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2184         my $recs = $_storage->request(
2185                         'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2186                         { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2187         )->gather(1);
2188
2189         return undef unless (@$recs);
2190
2191         return $_storage->request(
2192                 'open-ils.cstore.direct.biblio.record_entry.search.atomic',
2193                 { id => $recs->[0]->record }
2194         )->gather(1);
2195 }
2196 __PACKAGE__->register_method(
2197         method    => 'retrieve_isbn_object',
2198         api_name  => 'open-ils.supercat.isbn.object.retrieve',
2199         api_level => 1,
2200         argc      => 1,
2201         signature =>
2202                 { desc     => <<"                 DESC",
2203 Returns the Fieldmapper object representation of the requested bibliographic record
2204                   DESC
2205                   params   =>
2206                         [
2207                                 { name => 'isbn',
2208                                   desc => 'an ISBN',
2209                                   type => 'string' },
2210                         ],
2211                   'return' =>
2212                         { desc => 'The bib record',
2213                           type => 'object' }
2214                 }
2215 );
2216
2217
2218
2219 sub retrieve_metarecord_mods {
2220         my $self = shift;
2221         my $client = shift;
2222         my $rid = shift;
2223
2224         my $_storage = OpenSRF::AppSession->connect( 'open-ils.cstore' );
2225
2226         # Get the metarecord in question
2227         my $mr =
2228         $_storage->request(
2229                 'open-ils.cstore.direct.metabib.metarecord.retrieve' => $rid
2230         )->gather(1);
2231
2232         # Now get the map of all bib records for the metarecord
2233         my $recs =
2234         $_storage->request(
2235                 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
2236                 {metarecord => $rid}
2237         )->gather(1);
2238
2239         $logger->debug("Adding ".scalar(@$recs)." bib record to the MODS of the metarecord");
2240
2241         # and retrieve the lead (master) record as MODS
2242         my ($master) =
2243                 $self   ->method_lookup('open-ils.supercat.record.mods.retrieve')
2244                         ->run($mr->master_record);
2245         my $master_mods = $_parser->parse_string($master)->documentElement;
2246         $master_mods->setNamespace( "http://www.loc.gov/mods/", "mods" );
2247         $master_mods->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2248
2249         # ... and a MODS clone to populate, with guts removed.
2250         my $mods = $_parser->parse_string($master)->documentElement;
2251         $mods->setNamespace( "http://www.loc.gov/mods/", "mods" ); # modsCollection element
2252         $mods->setNamespace('http://www.loc.gov/mods/', undef, 1);
2253         ($mods) = $mods->findnodes('//mods:mods');
2254         #$mods->setNamespace( "http://www.loc.gov/mods/", "mods" ); # mods element
2255         $mods->removeChildNodes;
2256         $mods->setNamespace('http://www.loc.gov/mods/', undef, 1);
2257
2258         # Add the metarecord ID as a (locally defined) info URI
2259         my $recordInfo = $mods
2260                 ->ownerDocument
2261                 ->createElement("recordInfo");
2262
2263         my $recordIdentifier = $mods
2264                 ->ownerDocument
2265                 ->createElement("recordIdentifier");
2266
2267         my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
2268         $year += 1900;
2269         $month += 1;
2270
2271         my $id = $mr->id;
2272         $recordIdentifier->appendTextNode(
2273                 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:metabib-metarecord/$id", $month, $day)
2274         );
2275
2276         $recordInfo->appendChild($recordIdentifier);
2277         $mods->appendChild($recordInfo);
2278
2279         # Grab the title, author and ISBN for the master record and populate the metarecord
2280         my ($title) = $master_mods->findnodes( './mods:titleInfo[not(@type)]' );
2281         
2282         if ($title) {
2283                 $title->setNamespace( "http://www.loc.gov/mods/", "mods" );
2284                 $title->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2285                 $title = $mods->ownerDocument->importNode($title);
2286                 $mods->appendChild($title);
2287         }
2288
2289         my ($author) = $master_mods->findnodes( './mods:name[mods:role/mods:text[text()="creator"]]' );
2290         if ($author) {
2291                 $author->setNamespace( "http://www.loc.gov/mods/", "mods" );
2292                 $author->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2293                 $author = $mods->ownerDocument->importNode($author);
2294                 $mods->appendChild($author);
2295         }
2296
2297         my ($isbn) = $master_mods->findnodes( './mods:identifier[@type="isbn"]' );
2298         if ($isbn) {
2299                 $isbn->setNamespace( "http://www.loc.gov/mods/", "mods" );
2300                 $isbn->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2301                 $isbn = $mods->ownerDocument->importNode($isbn);
2302                 $mods->appendChild($isbn);
2303         }
2304
2305         # ... and loop over the constituent records
2306         for my $map ( @$recs ) {
2307
2308                 # get the MODS
2309                 my ($rec) =
2310                         $self   ->method_lookup('open-ils.supercat.record.mods.retrieve')
2311                                 ->run($map->source);
2312
2313                 my $part_mods = $_parser->parse_string($rec);
2314                 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", "mods" );
2315                 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2316                 ($part_mods) = $part_mods->findnodes('//mods:mods');
2317
2318                 for my $node ( ($part_mods->findnodes( './mods:subject' )) ) {
2319                         $node->setNamespace( "http://www.loc.gov/mods/", "mods" );
2320                         $node->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2321                         $node = $mods->ownerDocument->importNode($node);
2322                         $mods->appendChild( $node );
2323                 }
2324
2325                 my $relatedItem = $mods
2326                         ->ownerDocument
2327                         ->createElement("relatedItem");
2328
2329                 $relatedItem->setAttribute( type => 'constituent' );
2330
2331                 my $identifier = $mods
2332                         ->ownerDocument
2333                         ->createElement("identifier");
2334
2335                 $identifier->setAttribute( type => 'uri' );
2336
2337                 my $subRecordInfo = $mods
2338                         ->ownerDocument
2339                         ->createElement("recordInfo");
2340
2341                 my $subRecordIdentifier = $mods
2342                         ->ownerDocument
2343                         ->createElement("recordIdentifier");
2344
2345                 my $subid = $map->source;
2346                 $subRecordIdentifier->appendTextNode(
2347                         sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:biblio-record_entry/$subid",
2348                                 $month,
2349                                 $day
2350                         )
2351                 );
2352                 $subRecordInfo->appendChild($subRecordIdentifier);
2353
2354                 $relatedItem->appendChild( $subRecordInfo );
2355
2356                 my ($tor) = $part_mods->findnodes( './mods:typeOfResource' );
2357                 $tor->setNamespace( "http://www.loc.gov/mods/", "mods" );
2358                 $tor->setNamespace( "http://www.loc.gov/mods/", undef, 1 ) if ($tor);
2359                 $tor = $mods->ownerDocument->importNode($tor) if ($tor);
2360                 $relatedItem->appendChild($tor) if ($tor);
2361
2362                 if ( my ($part_isbn) = $part_mods->findnodes( './mods:identifier[@type="isbn"]' ) ) {
2363                         $part_isbn->setNamespace( "http://www.loc.gov/mods/", "mods" );
2364                         $part_isbn->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2365                         $part_isbn = $mods->ownerDocument->importNode($part_isbn);
2366                         $relatedItem->appendChild( $part_isbn );
2367
2368                         if (!$isbn) {
2369                                 $isbn = $mods->appendChild( $part_isbn->cloneNode(1) );
2370                         }
2371                 }
2372
2373                 $mods->appendChild( $relatedItem );
2374
2375         }
2376
2377         $_storage->disconnect;
2378
2379         return $U->entityize($mods->toString);
2380
2381 }
2382 __PACKAGE__->register_method(
2383         method    => 'retrieve_metarecord_mods',
2384         api_name  => 'open-ils.supercat.metarecord.mods.retrieve',
2385         api_level => 1,
2386         argc      => 1,
2387         signature =>
2388                 { desc     => <<"                 DESC",
2389 Returns the MODS representation of the requested metarecord
2390                   DESC
2391                   params   =>
2392                         [
2393                                 { name => 'metarecordId',
2394                                   desc => 'An OpenILS metabib::metarecord id',
2395                                   type => 'number' },
2396                         ],
2397                   'return' =>
2398                         { desc => 'The metarecord in MODS',
2399                           type => 'string' }
2400                 }
2401 );
2402
2403 sub list_metarecord_formats {
2404         my @list = (
2405                 { mods =>
2406                         { namespace_uri   => 'http://www.loc.gov/mods/',
2407                           docs            => 'http://www.loc.gov/mods/',
2408                           schema_location => 'http://www.loc.gov/standards/mods/mods.xsd',
2409                         }
2410                 }
2411         );
2412
2413         for my $type ( keys %metarecord_xslt ) {
2414                 push @list,
2415                         { $type => 
2416                                 { namespace_uri   => $metarecord_xslt{$type}{namespace_uri},
2417                                   docs            => $metarecord_xslt{$type}{docs},
2418                                   schema_location => $metarecord_xslt{$type}{schema_location},
2419                                 }
2420                         };
2421         }
2422
2423         return \@list;
2424 }
2425 __PACKAGE__->register_method(
2426         method    => 'list_metarecord_formats',
2427         api_name  => 'open-ils.supercat.metarecord.formats',
2428         api_level => 1,
2429         argc      => 0,
2430         signature =>
2431                 { desc     => <<"                 DESC",
2432 Returns the list of valid metarecord formats that supercat understands.
2433                   DESC
2434                   'return' =>
2435                         { desc => 'The format list',
2436                           type => 'array' }
2437                 }
2438 );
2439
2440
2441 sub list_authority_formats {
2442         my @list = (
2443                 { marcxml =>
2444                         { namespace_uri   => 'http://www.loc.gov/MARC21/slim',
2445                           docs            => 'http://www.loc.gov/marcxml/',
2446                           schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
2447                         }
2448                 }
2449         );
2450
2451 #       for my $type ( keys %record_xslt ) {
2452 #               push @list,
2453 #                       { $type => 
2454 #                               { namespace_uri   => $record_xslt{$type}{namespace_uri},
2455 #                                 docs            => $record_xslt{$type}{docs},
2456 #                                 schema_location => $record_xslt{$type}{schema_location},
2457 #                               }
2458 #                       };
2459 #       }
2460 #
2461         return \@list;
2462 }
2463 __PACKAGE__->register_method(
2464         method    => 'list_authority_formats',
2465         api_name  => 'open-ils.supercat.authority.formats',
2466         api_level => 1,
2467         argc      => 0,
2468         signature =>
2469                 { desc     => <<"                 DESC",
2470 Returns the list of valid authority formats that supercat understands.
2471                   DESC
2472                   'return' =>
2473                         { desc => 'The format list',
2474                           type => 'array' }
2475                 }
2476 );
2477
2478 sub list_record_formats {
2479         my @list = (
2480                 { marcxml =>
2481                         { namespace_uri   => 'http://www.loc.gov/MARC21/slim',
2482                           docs            => 'http://www.loc.gov/marcxml/',
2483                           schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
2484                         }
2485                 }
2486         );
2487
2488         for my $type ( keys %record_xslt ) {
2489                 push @list,
2490                         { $type => 
2491                                 { namespace_uri   => $record_xslt{$type}{namespace_uri},
2492                                   docs            => $record_xslt{$type}{docs},
2493                                   schema_location => $record_xslt{$type}{schema_location},
2494                                 }
2495                         };
2496         }
2497
2498         return \@list;
2499 }
2500 __PACKAGE__->register_method(
2501         method    => 'list_record_formats',
2502         api_name  => 'open-ils.supercat.record.formats',
2503         api_level => 1,
2504         argc      => 0,
2505         signature =>
2506                 { desc     => <<"                 DESC",
2507 Returns the list of valid record formats that supercat understands.
2508                   DESC
2509                   'return' =>
2510                         { desc => 'The format list',
2511                           type => 'array' }
2512                 }
2513 );
2514 __PACKAGE__->register_method(
2515         method    => 'list_record_formats',
2516         api_name  => 'open-ils.supercat.isbn.formats',
2517         api_level => 1,
2518         argc      => 0,
2519         signature =>
2520                 { desc     => <<"                 DESC",
2521 Returns the list of valid record formats that supercat understands.
2522                   DESC
2523                   'return' =>
2524                         { desc => 'The format list',
2525                           type => 'array' }
2526                 }
2527 );
2528
2529
2530 sub oISBN {
2531         my $self = shift;
2532         my $client = shift;
2533         my $isbn = shift;
2534
2535         $isbn =~ s/-//gso;
2536
2537         throw OpenSRF::EX::InvalidArg ('I need an ISBN please')
2538                 unless (length($isbn) >= 10);
2539
2540         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2541
2542         # Create a storage session, since we'll be making muliple requests.
2543         $_storage->connect;
2544
2545         # Find the record that has that ISBN.
2546         my $bibrec = $_storage->request(
2547                 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2548                 { tag => '020', subfield => 'a', value => { like => lc($isbn).'%'} }
2549         )->gather(1);
2550
2551         # Go away if we don't have one.
2552         return {} unless (@$bibrec);
2553
2554         # Find the metarecord for that bib record.
2555         my $mr = $_storage->request(
2556                 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
2557                 {source => $bibrec->[0]->record}
2558         )->gather(1);
2559
2560         # Find the other records for that metarecord.
2561         my $records = $_storage->request(
2562                 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
2563                 {metarecord => $mr->[0]->metarecord}
2564         )->gather(1);
2565
2566         # Just to be safe.  There's currently no unique constraint on sources...
2567         my %unique_recs = map { ($_->source, 1) } @$records;
2568         my @rec_list = sort keys %unique_recs;
2569
2570         # And now fetch the ISBNs for thos records.
2571         my $recs = [];
2572         push @$recs,
2573                 $_storage->request(
2574                         'open-ils.cstore.direct.metabib.full_rec.search',
2575                         { tag => '020', subfield => 'a', record => $_ }
2576                 )->gather(1) for (@rec_list);
2577
2578         # We're done with the storage server session.
2579         $_storage->disconnect;
2580
2581         # Return the oISBN data structure.  This will be XMLized at a higher layer.
2582         return
2583                 { metarecord => $mr->[0]->metarecord,
2584                   record_list => { map { $_ ? ($_->record, $_->value) : () } @$recs } };
2585
2586 }
2587 __PACKAGE__->register_method(
2588         method    => 'oISBN',
2589         api_name  => 'open-ils.supercat.oisbn',
2590         api_level => 1,
2591         argc      => 1,
2592         signature =>
2593                 { desc     => <<"                 DESC",
2594 Returns the ISBN list for the metarecord of the requested isbn
2595                   DESC
2596                   params   =>
2597                         [
2598                                 { name => 'isbn',
2599                                   desc => 'An ISBN.  Duh.',
2600                                   type => 'string' },
2601                         ],
2602                   'return' =>
2603                         { desc => 'record to isbn map',
2604                           type => 'object' }
2605                 }
2606 );
2607
2608 package OpenILS::Application::SuperCat::unAPI;
2609 use base qw/OpenILS::Application::SuperCat/;
2610
2611 sub as_xml {
2612     die "dummy superclass, use a real class";
2613 }
2614
2615 sub new {
2616     my $class = shift;
2617     my $obj = shift;
2618     return unless ($obj);
2619
2620     $class = ref($class) || $class;
2621
2622     if ($class eq __PACKAGE__) {
2623         return unless (ref($obj));
2624         $class .= '::' . $obj->json_hint;
2625     }
2626
2627     return bless { obj => $obj } => $class;
2628 }
2629
2630 sub obj {
2631     my $self = shift;
2632     return $self->{obj};
2633 }
2634
2635 package OpenILS::Application::SuperCat::unAPI::auri;
2636 use base qw/OpenILS::Application::SuperCat::unAPI/;
2637
2638 sub as_xml {
2639     my $self = shift;
2640     my $args = shift;
2641
2642     my $xml = '      <uri xmlns="http://open-ils.org/spec/holdings/v1" ';
2643     $xml .= 'id="tag:open-ils.org:asset-uri/' . $self->obj->id . '" ';
2644     $xml .= 'use_restriction="' . $self->escape( $self->obj->use_restriction ) . '" ';
2645     $xml .= 'label="' . $self->escape( $self->obj->label ) . '" ';
2646     $xml .= 'href="' . $self->escape( $self->obj->href ) . '">';
2647
2648     if (!$args->{no_volumes}) {
2649         if (ref($self->obj->call_number_maps) && @{ $self->obj->call_number_maps }) {
2650             $xml .= "      <volumes>\n" . join(
2651                 '',
2652                 map {
2653                     OpenILS::Application::SuperCat::unAPI
2654                         ->new( $_->call_number )
2655                         ->as_xml({ %$args, no_uris=>1, no_copies=>1 })
2656                 } @{ $self->obj->call_number_maps }
2657             ) . "      </volumes>\n";
2658
2659         } else {
2660             $xml .= "      <volumes/>\n";
2661         }
2662     }
2663
2664     $xml .= "      </uri>\n";
2665
2666     return $xml;
2667 }
2668
2669 package OpenILS::Application::SuperCat::unAPI::acn;
2670 use base qw/OpenILS::Application::SuperCat::unAPI/;
2671
2672 sub as_xml {
2673     my $self = shift;
2674     my $args = shift;
2675
2676     my $xml = '    <volume xmlns="http://open-ils.org/spec/holdings/v1" ';
2677
2678     $xml .= 'id="tag:open-ils.org:asset-call_number/' . $self->obj->id . '" ';
2679     $xml .= 'lib="' . $self->escape( $self->obj->owning_lib->shortname ) . '" ';
2680     $xml .= 'opac_visible="' . $self->obj->owning_lib->opac_visible . '" ';
2681     $xml .= 'label="' . $self->escape( $self->obj->label ) . '">';
2682     $xml .= "\n";
2683
2684     if (!$args->{no_copies}) {
2685         if (ref($self->obj->copies) && @{ $self->obj->copies }) {
2686             $xml .= "      <copies>\n" . join(
2687                 '',
2688                 map {
2689                     OpenILS::Application::SuperCat::unAPI
2690                         ->new( $_ )
2691                         ->as_xml({ %$args, no_volume=>1 })
2692                 } @{ $self->obj->copies }
2693             ) . "      </copies>\n";
2694
2695         } else {
2696             $xml .= "      <copies/>\n";
2697         }
2698     }
2699
2700     if (!$args->{no_uris}) {
2701         if (ref($self->obj->uri_maps) && @{ $self->obj->uri_maps }) {
2702             $xml .= "      <uris>\n" . join(
2703                 '',
2704                 map {
2705                     OpenILS::Application::SuperCat::unAPI
2706                         ->new( $_->uri )
2707                         ->as_xml({ %$args, no_volumes=>1 })
2708                 } @{ $self->obj->uri_maps }
2709             ) . "      </uris>\n";
2710
2711         } else {
2712             $xml .= "      <uris/>\n";
2713         }
2714     }
2715
2716
2717     $xml .= '      <owning_lib xmlns="http://open-ils.org/spec/actors/v1" ';
2718     $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->owning_lib->id . '" ';
2719     $xml .= 'shortname="'.$self->escape( $self->obj->owning_lib->shortname ) .'" ';
2720     $xml .= 'name="'.$self->escape( $self->obj->owning_lib->name ) .'"/>';
2721     $xml .= "\n";
2722
2723     unless ($args->{no_record}) {
2724         my $rec_tag = "tag:open-ils.org:biblio-record_entry/".$self->obj->record->id.'/'.$self->escape( $self->obj->owning_lib->shortname ) ;
2725
2726         my $r_doc = $parser->parse_string($self->obj->record->marc);
2727         $r_doc->documentElement->setAttribute( id => $rec_tag );
2728         $xml .= $U->entityize($r_doc->documentElement->toString);
2729     }
2730
2731     $xml .= "    </volume>\n";
2732
2733     return $xml;
2734 }
2735
2736 package OpenILS::Application::SuperCat::unAPI::ssub;
2737 use base qw/OpenILS::Application::SuperCat::unAPI/;
2738
2739 sub as_xml {
2740     my $self = shift;
2741     my $args = shift;
2742
2743     my $xml = '    <subscription xmlns="http://open-ils.org/spec/holdings/v1" ';
2744
2745     $xml .= 'id="tag:open-ils.org:serial-subscription/' . $self->obj->id . '" ';
2746     $xml .= 'start="' . $self->escape( $self->obj->start_date ) . '" ';
2747     $xml .= 'end="' . $self->escape( $self->obj->end_date ) . '" ';
2748     $xml .= 'expected_date_offset="' . $self->escape( $self->obj->expected_date_offset ) . '">';
2749     $xml .= "\n";
2750
2751     if (!$args->{no_distributions}) {
2752         if (ref($self->obj->distributions) && @{ $self->obj->distributions }) {
2753             $xml .= "      <distributions>\n" . join(
2754                 '',
2755                 map {
2756                     OpenILS::Application::SuperCat::unAPI
2757                         ->new( $_ )
2758                         ->as_xml({ %$args, no_subscription=>1, no_issuance=>1 })
2759                 } @{ $self->obj->distributions }
2760             ) . "      </distributions>\n";
2761
2762         } else {
2763             $xml .= "      <distributions/>\n";
2764         }
2765     }
2766
2767     if (!$args->{no_captions_and_patterns}) {
2768         if (ref($self->obj->captions_and_patterns) && @{ $self->obj->captions_and_patterns }) {
2769             $xml .= "      <captions_and_patterns>\n" . join(
2770                 '',
2771                 map {
2772                     OpenILS::Application::SuperCat::unAPI
2773                         ->new( $_ )
2774                         ->as_xml({ %$args, no_subscription=>1 })
2775                 } @{ $self->obj->captions_and_patterns }
2776             ) . "      </captions_and_patterns>\n";
2777
2778         } else {
2779             $xml .= "      <captions_and_patterns/>\n";
2780         }
2781     }
2782
2783     if (!$args->{no_issuances}) {
2784         if (ref($self->obj->issuances) && @{ $self->obj->issuances }) {
2785             $xml .= "      <issuances>\n" . join(
2786                 '',
2787                 map {
2788                     OpenILS::Application::SuperCat::unAPI
2789                         ->new( $_ )
2790                         ->as_xml({ %$args, no_subscription=>1, no_items=>1 })
2791                 } @{ $self->obj->issuances }
2792             ) . "      </issuances>\n";
2793
2794         } else {
2795             $xml .= "      <issuances/>\n";
2796         }
2797     }
2798
2799
2800     $xml .= '      <owning_lib xmlns="http://open-ils.org/spec/actors/v1" ';
2801     $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->owning_lib->id . '" ';
2802     $xml .= 'shortname="'.$self->escape( $self->obj->owning_lib->shortname ) .'" ';
2803     $xml .= 'name="'.$self->escape( $self->obj->owning_lib->name ) .'"/>';
2804     $xml .= "\n";
2805
2806     unless ($args->{no_record}) {
2807         my $rec_tag = "tag:open-ils.org:biblio-record_entry/".$self->obj->record->id.'/'.$self->escape( $self->obj->owning_lib->shortname ) ;
2808
2809         my $r_doc = $parser->parse_string($self->obj->record_entry->marc);
2810         $r_doc->documentElement->setAttribute( id => $rec_tag );
2811         $xml .= $U->entityize($r_doc->documentElement->toString);
2812     }
2813
2814     $xml .= "    </subscription>\n";
2815
2816     return $xml;
2817 }
2818
2819 package OpenILS::Application::SuperCat::unAPI::sdist;
2820 use base qw/OpenILS::Application::SuperCat::unAPI/;
2821
2822 sub as_xml {
2823     my $self = shift;
2824     my $args = shift;
2825
2826     my $xml = '    <distribution xmlns="http://open-ils.org/spec/holdings/v1" ';
2827
2828     $xml .= 'id="tag:open-ils.org:serial-distribution/' . $self->obj->id . '" ';
2829     $xml .= 'label="' . $self->escape( $self->obj->label ) . '" ';
2830     $xml .= 'unit_label_base="' . $self->escape( $self->obj->unit_label_base ) . '" ';
2831     $xml .= 'unit_label_suffix="' . $self->escape( $self->obj->unit_label_suffix ) . '">';
2832     $xml .= "\n";
2833
2834     if (!$args->{no_distributions}) {
2835         if (ref($self->obj->distributions) && @{ $self->obj->distributions }) {
2836             $xml .= "      <streams>\n" . join(
2837                 '',
2838                 map {
2839                     OpenILS::Application::SuperCat::unAPI
2840                         ->new( $_ )
2841                         ->as_xml({ %$args, no_distribution=>1 })
2842                 } @{ $self->obj->streams }
2843             ) . "      </streams>\n";
2844
2845         } else {
2846             $xml .= "      <streams/>\n";
2847         }
2848     }
2849
2850     if (!$args->{no_summaries}) {
2851         $xml .= "      <summaries>\n";
2852         if (ref($self->obj->bib_summaries) && @{ $self->obj->bib_summaries }) {
2853             $xml .= join ('',
2854                 map {
2855                     OpenILS::Application::SuperCat::unAPI
2856                         ->new( $_ )
2857                         ->as_xml({ %$args, no_distribution=>1 })
2858                 } ( @{ $self->obj->bib_summaries }, @{ $self->obj->sup_summaries }, @{ $self->obj->index_summaries } )
2859             );
2860
2861         }
2862                 $xml .= "      </summaries>\n";
2863         }
2864
2865
2866     $xml .= '      <holding_lib xmlns="http://open-ils.org/spec/actors/v1" ';
2867     $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->owning_lib->id . '" ';
2868     $xml .= 'shortname="'.$self->escape( $self->obj->owning_lib->shortname ) .'" ';
2869     $xml .= 'name="'.$self->escape( $self->obj->owning_lib->name ) .'"/>';
2870     $xml .= "\n";
2871
2872         $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_distributions=>1 }) if (!$args->{no_subscription});
2873
2874     if (!$args->{no_record} && $self->obj->record_entry) {
2875         my $rec_tag = "tag:open-ils.org:serial-record_entry/".$self->obj->record->id ;
2876
2877         my $r_doc = $parser->parse_string($self->obj->record_entry->marc);
2878         $r_doc->documentElement->setAttribute( id => $rec_tag );
2879         $xml .= $U->entityize($r_doc->documentElement->toString);
2880     }
2881
2882     $xml .= "    </distribution>\n";
2883
2884     return $xml;
2885 }
2886
2887 package OpenILS::Application::SuperCat::unAPI::sstr;
2888 use base qw/OpenILS::Application::SuperCat::unAPI/;
2889
2890 sub as_xml {
2891     my $self = shift;
2892     my $args = shift;
2893
2894     my $xml = '    <stream xmlns="http://open-ils.org/spec/holdings/v1" ';
2895
2896     $xml .= 'id="tag:open-ils.org:serial-stream/' . $self->obj->id . '" ';
2897     $xml .= 'routing_label="' . $self->escape( $self->obj->routing_label ) . '">';
2898     $xml .= "\n";
2899
2900     if (!$args->{no_items}) {
2901         if (ref($self->obj->items) && @{ $self->obj->items }) {
2902             $xml .= "      <items>\n" . join(
2903                 '',
2904                 map {
2905                     OpenILS::Application::SuperCat::unAPI
2906                         ->new( $_ )
2907                         ->as_xml({ %$args, no_stream=>1 })
2908                 } @{ $self->obj->items }
2909             ) . "      </items>\n";
2910
2911         } else {
2912             $xml .= "      <items/>\n";
2913         }
2914     }
2915
2916         #XXX routing_list_user's?
2917
2918         $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->distribution )->as_xml({ %$args, no_streams=>1 }) if (!$args->{no_distribution});
2919
2920     $xml .= "    </stream>\n";
2921
2922     return $xml;
2923 }
2924
2925 package OpenILS::Application::SuperCat::unAPI::sitem;
2926 use base qw/OpenILS::Application::SuperCat::unAPI/;
2927
2928 sub as_xml {
2929     my $self = shift;
2930     my $args = shift;
2931
2932     my $xml = '    <serial_item xmlns="http://open-ils.org/spec/holdings/v1" ';
2933
2934     $xml .= 'id="tag:open-ils.org:serial-item/' . $self->obj->id . '" ';
2935     $xml .= 'date_expected="' . $self->escape( $self->obj->date_expected ) . '"';
2936     $xml .= ' date_received="' . $self->escape( $self->obj->date_received ) .'"'if ($self->obj->date_received);
2937
2938         if ($args->{no_issuance}) {
2939                 my $siss = ref($self->obj->issuance) ? $self->obj->issuance->id : $self->obj->issuance;
2940             $xml .= ' issuance="tag:open-ils.org:serial-issuance/' . $siss . '"';
2941         }
2942
2943     $xml .= ">\n";
2944
2945         if (ref($self->obj->notes) && $self->obj->notes) {
2946                 $xml .= "        <notes>\n";
2947                 for my $note ( @{$self->obj->notes} ) {
2948                         next unless ( $note->pub eq 't' );
2949                         $xml .= sprintf('        <note date="%s" title="%s">%s</note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
2950                         $xml .= "\n";
2951                 }
2952                 $xml .= "        </notes>\n";
2953     } else {
2954         $xml .= "      <notes/>\n";
2955         }
2956
2957         $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->issuance )->as_xml({ %$args, no_items=>1 }) if (!$args->{no_issuance});
2958         $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->stream )->as_xml({ %$args, no_items=>1 }) if (!$args->{no_stream});
2959         $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->unit )->as_xml({ %$args, no_items=>1, no_volumes=>1 }) if (!$args->{no_unit});
2960         $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->uri )->as_xml({ %$args, no_items=>1, no_volumes=>1 }) if (!$args->{no_uri});
2961
2962     $xml .= "    </stream>\n";
2963
2964     return $xml;
2965 }
2966
2967 package OpenILS::Application::SuperCat::unAPI::sunit;
2968 use base qw/OpenILS::Application::SuperCat::unAPI/;
2969
2970 sub as_xml {
2971     my $self = shift;
2972     my $args = shift;
2973
2974     my $xml = '      <serial_item xmlns="http://open-ils.org/spec/holdings/v1" '.
2975         'id="tag:open-ils.org:serial-unit/' . $self->obj->id . '" ';
2976
2977     $xml .= $_ . '="' . $self->escape( $self->obj->$_  ) . '" ' for (qw/
2978         create_date edit_date copy_number circulate deposit ref holdable deleted
2979         deposit_amount price barcode circ_modifier circ_as_type opac_visible
2980                 status_changed_time floating mint_condition label label_sort_key contents
2981     /);
2982
2983     $xml .= ">\n";
2984
2985     $xml .= '        <status ident="' . $self->obj->status->id . '">' . $self->escape( $self->obj->status->name  ) . "</status>\n";
2986     $xml .= '        <location ident="' . $self->obj->location->id . '">' . $self->escape( $self->obj->location->name  ) . "</location>\n";
2987     $xml .= '        <circlib ident="' . $self->obj->circ_lib->id . '">' . $self->escape( $self->obj->circ_lib->name  ) . "</circlib>\n";
2988
2989     $xml .= '        <circ_lib xmlns="http://open-ils.org/spec/actors/v1" ';
2990     $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->circ_lib->id . '" ';
2991     $xml .= 'shortname="'.$self->escape( $self->obj->circ_lib->shortname ) .'" ';
2992     $xml .= 'name="'.$self->escape( $self->obj->circ_lib->name ) .'"/>';
2993     $xml .= "\n";
2994
2995         $xml .= "        <copy_notes>\n";
2996         if (ref($self->obj->notes) && $self->obj->notes) {
2997                 for my $note ( @{$self->obj->notes} ) {
2998                         next unless ( $note->pub eq 't' );
2999                         $xml .= sprintf('        <copy_note date="%s" title="%s">%s</copy_note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3000                         $xml .= "\n";
3001                 }
3002         }
3003
3004         $xml .= "        </copy_notes>\n";
3005     $xml .= "        <statcats>\n";
3006
3007         if (ref($self->obj->stat_cat_entries) && $self->obj->stat_cat_entries) {
3008                 for my $sce ( @{$self->obj->stat_cat_entries} ) {
3009                         next unless ( $sce->stat_cat->opac_visible eq 't' );
3010                         $xml .= sprintf('          <statcat name="%s">%s</statcat>',$self->escape($sce->stat_cat->name) ,$self->escape($sce->value));
3011                         $xml .= "\n";
3012                 }
3013         }
3014         $xml .= "        </statcats>\n";
3015
3016     unless ($args->{no_volume}) {
3017         if (ref($self->obj->call_number)) {
3018             $xml .= OpenILS::Application::SuperCat::unAPI
3019                         ->new( $self->obj->call_number )
3020                         ->as_xml({ %$args, no_copies=>1 });
3021         } else {
3022             $xml .= "    <volume/>\n";
3023         }
3024     }
3025
3026     $xml .= "      </serial_item>\n";
3027
3028     return $xml;
3029 }
3030
3031 package OpenILS::Application::SuperCat::unAPI::sercap;
3032 use base qw/OpenILS::Application::SuperCat::unAPI/;
3033
3034 sub as_xml {
3035     my $self = shift;
3036     my $args = shift;
3037
3038     my $xml = '      <caption_and_pattern xmlns="http://open-ils.org/spec/holdings/v1" '.
3039         'id="tag:open-ils.org:serial-caption_and_pattern/' . $self->obj->id . '" ';
3040
3041     $xml .= $_ . '="' . $self->escape( $self->obj->$_  ) . '" ' for (qw/
3042         create_time type active pattern_code enum_1 enum_2 enum_3 enum_4
3043                 enum_5 enum_6 chron_1 chron_2 chron_3 chron_4 chron_5
3044     /);
3045     $xml .= ">\n";
3046         $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_captions_and_patterns=>1 }) if (!$args->{no_subscription});
3047     $xml .= "      </caption_and_pattern>\n";
3048
3049     return $xml;
3050 }
3051
3052 package OpenILS::Application::SuperCat::unAPI::siss;
3053 use base qw/OpenILS::Application::SuperCat::unAPI/;
3054
3055 sub as_xml {
3056     my $self = shift;
3057     my $args = shift;
3058
3059     my $xml = '      <issuance xmlns="http://open-ils.org/spec/holdings/v1" '.
3060         'id="tag:open-ils.org:serial-issuance/' . $self->obj->id . '" ';
3061
3062     $xml .= $_ . '="' . $self->escape( $self->obj->$_  ) . '" '
3063                 for (qw/create_date edit_date label date_published holding_code holding_type holding_link_id/);
3064
3065     $xml .= ">\n";
3066
3067     if (!$args->{no_items}) {
3068         if (ref($self->obj->items) && @{ $self->obj->items }) {
3069             $xml .= "      <items>\n" . join(
3070                 '',
3071                 map {
3072                     OpenILS::Application::SuperCat::unAPI
3073                         ->new( $_ )
3074                         ->as_xml({ %$args, no_stream=>1 })
3075                 } @{ $self->obj->items }
3076             ) . "      </items>\n";
3077
3078         } else {
3079             $xml .= "      <items/>\n";
3080         }
3081     }
3082
3083         $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_issuances=>1 }) if (!$args->{no_subscription});
3084     $xml .= "      </issuance>\n";
3085
3086     return $xml;
3087 }
3088
3089 package OpenILS::Application::SuperCat::unAPI::acp;
3090 use base qw/OpenILS::Application::SuperCat::unAPI/;
3091
3092 sub as_xml {
3093     my $self = shift;
3094     my $args = shift;
3095
3096     my $xml = '      <copy xmlns="http://open-ils.org/spec/holdings/v1" '.
3097         'id="tag:open-ils.org:asset-copy/' . $self->obj->id . '" ';
3098
3099     $xml .= $_ . '="' . $self->escape( $self->obj->$_  ) . '" ' for (qw/
3100         create_date edit_date copy_number circulate deposit ref holdable deleted
3101         deposit_amount price barcode circ_modifier circ_as_type opac_visible
3102     /);
3103
3104     $xml .= ">\n";
3105
3106     $xml .= '        <status ident="' . $self->obj->status->id . '">' . $self->escape( $self->obj->status->name  ) . "</status>\n";
3107     $xml .= '        <location ident="' . $self->obj->location->id . '" opac_visible="'.$self->obj->location->opac_visible.'">' . $self->escape( $self->obj->location->name  ) . "</location>\n";
3108     $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";
3109
3110     $xml .= '        <circ_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3111     $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->circ_lib->id . '" ';
3112     $xml .= 'shortname="'.$self->escape( $self->obj->circ_lib->shortname ) .'" ';
3113     $xml .= 'name="'.$self->escape( $self->obj->circ_lib->name ) .'" opac_visible="'.$self->obj->circ_lib->opac_visible.'"/>';
3114     $xml .= "\n";
3115
3116         $xml .= "        <copy_notes>\n";
3117         if (ref($self->obj->notes) && $self->obj->notes) {
3118                 for my $note ( @{$self->obj->notes} ) {
3119                         next unless ( $note->pub eq 't' );
3120                         $xml .= sprintf('        <copy_note date="%s" title="%s">%s</copy_note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3121                         $xml .= "\n";
3122                 }
3123         }
3124
3125         $xml .= "        </copy_notes>\n";
3126     $xml .= "        <statcats>\n";
3127
3128         if (ref($self->obj->stat_cat_entries) && $self->obj->stat_cat_entries) {
3129                 for my $sce ( @{$self->obj->stat_cat_entries} ) {
3130                         next unless ( $sce->stat_cat->opac_visible eq 't' );
3131                         $xml .= sprintf('          <statcat name="%s">%s</statcat>',$self->escape($sce->stat_cat->name) ,$self->escape($sce->value));
3132                         $xml .= "\n";
3133                 }
3134         }
3135         $xml .= "        </statcats>\n";
3136
3137     unless ($args->{no_volume}) {
3138         if (ref($self->obj->call_number)) {
3139             $xml .= OpenILS::Application::SuperCat::unAPI
3140                         ->new( $self->obj->call_number )
3141                         ->as_xml({ %$args, no_copies=>1 });
3142         } else {
3143             $xml .= "    <volume/>\n";
3144         }
3145     }
3146
3147     $xml .= "      </copy>\n";
3148
3149     return $xml;
3150 }
3151
3152
3153 1;
3154 # vim: noet:ts=4:sw=4