]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/SuperCat.pm
we should not restrict to the top org only, get all counts
[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 => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label] } },
302                           owning_lib    => \@ou_ids,
303               deleted => 'f',
304               @cp_filter
305                         },
306                         { flesh         => 1,
307                           flesh_fields  => { acn => [qw/record owning_lib/] },
308                           order_by      => { acn => "oils_text_as_bytea(label_sortkey) desc, oils_text_as_bytea(label) desc, id desc, owning_lib desc" },
309                           limit         => $before_limit,
310                           offset        => abs($page) * $page_size - $before_offset,
311                         }
312                 )->gather(1);
313                 push @list, reverse(@$before);
314         }
315
316         if ($page >= 0) {
317                 my $after = $_storage->request(
318                         "open-ils.cstore.direct.asset.call_number.search.atomic",
319                         { label         => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label] } },
320                           owning_lib    => \@ou_ids,
321               deleted => 'f',
322               @cp_filter
323                         },
324                         { flesh         => 1,
325                           flesh_fields  => { acn => [qw/record owning_lib/] },
326                           order_by      => { acn => "oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib" },
327                           limit         => $after_limit,
328                           offset        => abs($page) * $page_size - $after_offset,
329                         }
330                 )->gather(1);
331                 push @list, @$after;
332         }
333
334         return \@list;
335 }
336 __PACKAGE__->register_method(
337         method    => 'cn_browse',
338         api_name  => 'open-ils.supercat.call_number.browse',
339         api_level => 1,
340         argc      => 1,
341         signature =>
342                 { desc     => <<"                 DESC",
343 Returns the XML representation of the requested bibliographic record's holdings
344                   DESC
345                   params   =>
346                         [
347                                 { name => 'label',
348                                   desc => 'The target call number lable',
349                                   type => 'string' },
350                                 { name => 'org_unit',
351                                   desc => 'The org unit shortname (or "-" or undef for global) to browse',
352                                   type => 'string' },
353                                 { name => 'page_size',
354                                   desc => 'Count of call numbers to retrieve, default is 9',
355                                   type => 'number' },
356                                 { name => 'page',
357                                   desc => 'The page of call numbers to retrieve, calculated based on page_size.  Can be positive, negative or 0.',
358                                   type => 'number' },
359                                 { name => 'statuses',
360                                   desc => 'Array of statuses to filter copies by, optional and can be undef.',
361                                   type => 'array' },
362                                 { name => 'locations',
363                                   desc => 'Array of copy locations to filter copies by, optional and can be undef.',
364                                   type => 'array' },
365                         ],
366                   'return' =>
367                         { desc => 'Call numbers with owning_lib and record fleshed',
368                           type => 'array' }
369                 }
370 );
371
372 sub cn_startwith {
373         my $self = shift;
374         my $client = shift;
375
376         my $label = shift;
377         my $ou = shift;
378         my $limit = shift || 10;
379         my $page = shift || 0;
380         my $statuses = shift || [];
381         my $copy_locations = shift || [];
382
383
384         my $offset = abs($page) * $limit;
385         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
386
387         my $o_search = { shortname => $ou };
388         if (!$ou || $ou eq '-') {
389                 $o_search = { parent_ou => undef };
390         }
391
392         my $orgs = $_storage->request(
393                 "open-ils.cstore.direct.actor.org_unit.search",
394                 $o_search,
395                 { flesh         => 100,
396                   flesh_fields  => { aou        => [qw/children/] }
397                 }
398         )->gather(1);
399
400         my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
401
402         $logger->debug("Searching for CNs at orgs [".join(',',@ou_ids)."], based on $ou");
403
404         my @list = ();
405
406     my @cp_filter = ();
407     if (@$statuses || @$copy_locations) {
408         @cp_filter = (
409             '-exists' => {
410                 from  => 'acp',
411                                 where => {
412                     call_number => { '=' => { '+acn' => 'id' } },
413                     deleted     => 'f',
414                     ((@$statuses)       ? ( status   => $statuses)       : ()),
415                                     ((@$copy_locations) ? ( location => $copy_locations) : ())
416                 }
417             }
418         );
419     }
420
421         if ($page < 0) {
422                 my $before = $_storage->request(
423                         "open-ils.cstore.direct.asset.call_number.search.atomic",
424                         { label         => { "<" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label] } },
425                           owning_lib    => \@ou_ids,
426               deleted => 'f',
427               @cp_filter
428                         },
429                         { flesh         => 1,
430                           flesh_fields  => { acn => [qw/record owning_lib/] },
431                           order_by      => { acn => "oils_text_as_bytea(label_sortkey) desc, oils_text_as_bytea(label) desc, id desc, owning_lib desc" },
432                           limit         => $limit,
433                           offset        => $offset,
434                         }
435                 )->gather(1);
436                 push @list, reverse(@$before);
437         }
438
439         if ($page >= 0) {
440                 my $after = $_storage->request(
441                         "open-ils.cstore.direct.asset.call_number.search.atomic",
442                         { label         => { ">=" => { transform => "oils_text_as_bytea", value => ["oils_text_as_bytea", $label] } },
443                           owning_lib    => \@ou_ids,
444               deleted => 'f',
445               @cp_filter
446                         },
447                         { flesh         => 1,
448                           flesh_fields  => { acn => [qw/record owning_lib/] },
449                           order_by      => { acn => "oils_text_as_bytea(label_sortkey), oils_text_as_bytea(label), id, owning_lib" },
450                           limit         => $limit,
451                           offset        => $offset,
452                         }
453                 )->gather(1);
454                 push @list, @$after;
455         }
456
457         return \@list;
458 }
459 __PACKAGE__->register_method(
460         method    => 'cn_startwith',
461         api_name  => 'open-ils.supercat.call_number.startwith',
462         api_level => 1,
463         argc      => 1,
464         signature =>
465                 { desc     => <<"                 DESC",
466 Returns the XML representation of the requested bibliographic record's holdings
467                   DESC
468                   params   =>
469                         [
470                                 { name => 'label',
471                                   desc => 'The target call number lable',
472                                   type => 'string' },
473                                 { name => 'org_unit',
474                                   desc => 'The org unit shortname (or "-" or undef for global) to browse',
475                                   type => 'string' },
476                                 { name => 'page_size',
477                                   desc => 'Count of call numbers to retrieve, default is 9',
478                                   type => 'number' },
479                                 { name => 'page',
480                                   desc => 'The page of call numbers to retrieve, calculated based on page_size.  Can be positive, negative or 0.',
481                                   type => 'number' },
482                                 { name => 'statuses',
483                                   desc => 'Array of statuses to filter copies by, optional and can be undef.',
484                                   type => 'array' },
485                                 { name => 'locations',
486                                   desc => 'Array of copy locations to filter copies by, optional and can be undef.',
487                                   type => 'array' },
488                         ],
489                   'return' =>
490                         { desc => 'Call numbers with owning_lib and record fleshed',
491                           type => 'array' }
492                 }
493 );
494
495
496 sub new_books_by_item {
497         my $self = shift;
498         my $client = shift;
499
500         my $ou = shift;
501         my $page_size = shift || 10;
502         my $page = shift || 1;
503         my $statuses = shift || [];
504         my $copy_locations = shift || [];
505
506     my $offset = $page_size * ($page - 1);
507
508         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
509
510         my @ou_ids;
511         if ($ou && $ou ne '-') {
512                 my $orgs = $_storage->request(
513                         "open-ils.cstore.direct.actor.org_unit.search",
514                         { shortname => $ou },
515                         { flesh         => 100,
516                           flesh_fields  => { aou        => [qw/children/] }
517                         }
518                 )->gather(1);
519                 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
520         }
521
522         $logger->debug("Searching for records with new copies at orgs [".join(',',@ou_ids)."], based on $ou");
523         my $cns = $_storage->request(
524                 "open-ils.cstore.json_query.atomic",
525                 { select        => { acn => ['record'],
526                          acp => [{ aggregate => 1 => transform => max => column => create_date => alias => 'create_date'}]
527                        },
528                   from          => { 'acn' => { 'acp' => { field => call_number => fkey => 'id' } } },
529                   where         =>
530                         { '+acp' =>
531                                 { deleted => 'f',
532                                   ((@ou_ids)          ? ( circ_lib => \@ou_ids)        : ()),
533                                   ((@$statuses)       ? ( status   => $statuses)       : ()),
534                                   ((@$copy_locations) ? ( location => $copy_locations) : ())
535                                 }, 
536                           '+acn' => { record => { '>' => 0 } },
537                         }, 
538                   order_by      => { acp => { create_date => { transform => 'max', direction => 'desc' } } },
539                   limit         => $page_size,
540                   offset        => $offset
541                 }
542         )->gather(1);
543
544         return [ map { $_->{record} } @$cns ];
545 }
546 __PACKAGE__->register_method(
547         method    => 'new_books_by_item',
548         api_name  => 'open-ils.supercat.new_book_list',
549         api_level => 1,
550         argc      => 1,
551         signature =>
552                 { desc     => <<"                 DESC",
553 Returns the XML representation of the requested bibliographic record's holdings
554                   DESC
555                   params   =>
556                         [
557                                 { name => 'org_unit',
558                                   desc => 'The org unit shortname (or "-" or undef for global) to list',
559                                   type => 'string' },
560                                 { name => 'page_size',
561                                   desc => 'Count of records to retrieve, default is 10',
562                                   type => 'number' },
563                                 { name => 'page',
564                                   desc => 'The page of records to retrieve, calculated based on page_size.  Starts at 1.',
565                                   type => 'number' },
566                                 { name => 'statuses',
567                                   desc => 'Array of statuses to filter copies by, optional and can be undef.',
568                                   type => 'array' },
569                                 { name => 'locations',
570                                   desc => 'Array of copy locations to filter copies by, optional and can be undef.',
571                                   type => 'array' },
572                         ],
573                   'return' =>
574                         { desc => 'Record IDs',
575                           type => 'array' }
576                 }
577 );
578
579
580 sub general_browse {
581         my $self = shift;
582         my $client = shift;
583     return tag_sf_browse($self, $client, $self->{tag}, $self->{subfield}, @_);
584 }
585 __PACKAGE__->register_method(
586         method    => 'general_browse',
587         api_name  => 'open-ils.supercat.title.browse',
588         tag       => 'tnf', subfield => 'a',
589         api_level => 1,
590         argc      => 1,
591         signature =>
592                 { desc     => "Returns a list of the requested org-scoped record ids held",
593                   params   =>
594                         [ { name => 'value', desc => 'The target title', type => 'string' },
595                           { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
596                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
597                           { name => 'page', desc => 'The page of records 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         my $_search = OpenSRF::AppSession->create( 'open-ils.search' );
1713
1714         my $o_search = { shortname => uc($ou) };
1715         if (!$ou || $ou eq '-') {
1716                 $o_search = { parent_ou => undef };
1717         }
1718
1719     my $one_org = $_storage->request(
1720         "open-ils.cstore.direct.actor.org_unit.search",
1721         $o_search
1722     )->gather(1);
1723
1724     my $count_req = $_search->request('open-ils.search.biblio.record.copy_count' => $one_org->id => $bib);
1725     my $staff_count_req = $_search->request('open-ils.search.biblio.record.copy_count.staff' => $one_org->id => $bib);
1726
1727     my $orgs = $_storage->request(
1728         'open-ils.cstore.json_query.atomic',
1729         { from => [ 'actor.org_unit_descendants', defined($depth) ? ( $one_org->id, $depth ) :  ( $one_org->id ) ] }
1730     )->gather(1);
1731
1732
1733         my @ou_ids = map { $_->{id} } @$orgs;
1734
1735         $logger->info("Searching for holdings at orgs [".join(',',@ou_ids)."], based on $ou");
1736
1737     my %subselect = ( '-or' => [
1738         { owning_lib => \@ou_ids },
1739         { '-exists'  =>
1740             { from  => 'acp',
1741               where => {
1742                 call_number => { '=' => {'+acn'=>'id'} },
1743                 deleted => 'f',
1744                 circ_lib => \@ou_ids
1745               }
1746             }
1747         }
1748     ]);
1749
1750     if ($flesh and $flesh eq 'uris') {
1751         %subselect = (
1752             owning_lib => \@ou_ids,
1753             '-exists'  => {
1754                 from  => { auricnm => 'auri' },
1755                 where => {
1756                     call_number => { '=' => {'+acn'=>'id'} },
1757                     '+auri' => { active => 't' }
1758                 }
1759             }
1760         );
1761     }
1762
1763
1764         my $cns = $_storage->request(
1765                 "open-ils.cstore.direct.asset.call_number.search.atomic",
1766                 { record  => $bib,
1767           deleted => 'f',
1768           %subselect
1769         },
1770                 { flesh         => 5,
1771                   flesh_fields  => {
1772                                         acn     => [qw/copies owning_lib uri_maps/],
1773                                         auricnm => [qw/uri/],
1774                                         acp     => [qw/circ_lib location status stat_cat_entries notes/],
1775                                         asce    => [qw/stat_cat/],
1776                                 },
1777           ( $limit > -1 ? ( limit  => $limit  ) : () ),
1778           ( $offset     ? ( offset => $offset ) : () ),
1779           order_by  => { acn => { label_sortkey => {} } }
1780                 }
1781         )->gather(1);
1782
1783         my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
1784         $year += 1900;
1785         $month += 1;
1786
1787         $client->respond("<holdings xmlns='http://open-ils.org/spec/holdings/v1'><counts>\n");
1788
1789         my $copy_counts = $count_req->gather(1);
1790         my $staff_copy_counts = $staff_count_req->gather(1);
1791
1792         for my $c (@$copy_counts) {
1793                 $$c{transcendant} ||= 0;
1794                 my $out = "<count type='public'";
1795                 $out .= " $_='$$c{$_}'" for (qw/count available unshadow transcendant org_unit depth/);
1796                 $client->respond("$out/>\n")
1797         }
1798
1799         for my $c (@$staff_copy_counts) {
1800                 $$c{transcendant} ||= 0;
1801                 my $out = "<count type='staff'";
1802                 $out .= " $_='$$c{$_}'" for (qw/count available unshadow transcendant org_unit depth/);
1803                 $client->respond("$out/>\n")
1804         }
1805
1806     $client->respond("</counts><volumes>\n");
1807     
1808         for my $cn (@$cns) {
1809                 next unless (@{$cn->copies} > 0 or (ref($cn->uri_maps) and @{$cn->uri_maps}));
1810
1811                 # We don't want O:A:S:unAPI::acn to return the record, we've got that already
1812                 # In the context of BibTemplate, copies aren't necessary because we pull those
1813                 # in a separate call
1814         $client->respond(
1815             OpenILS::Application::SuperCat::unAPI::acn
1816                 ->new( $cn )
1817                 ->as_xml( {no_record => 1, no_copies => ($flesh ? 0 : 1)} )
1818         );
1819         }
1820
1821         $client->respond("</volumes><subscriptions>\n");
1822
1823         $logger->info("Searching for serial holdings at orgs [".join(',',@ou_ids)."], based on $ou");
1824
1825     %subselect = ( '-or' => [
1826         { owning_lib => \@ou_ids },
1827         { '-exists'  =>
1828             { from  => 'sdist',
1829               where => { holding_lib => \@ou_ids }
1830             }
1831         }
1832     ]);
1833
1834         my $ssubs = $_storage->request(
1835                 "open-ils.cstore.direct.serial.subscription.search.atomic",
1836                 { record_entry  => $bib,
1837           %subselect
1838         },
1839                 { flesh         => 5,
1840                   flesh_fields  => {
1841                                         ssub    => [qw/distributions issuances scaps owning_lib/],
1842                                         sdist   => [qw/basic_summary supplement_summary index_summary streams holding_lib/],
1843                                         sstr    => [qw/items/],
1844                                         sitem   => [qw/notes unit/],
1845                                 },
1846           ( $limit > -1 ? ( limit  => $limit  ) : () ),
1847           ( $offset     ? ( offset => $offset ) : () ),
1848           order_by  => {
1849                         ssub => {
1850                                 start_date => {},
1851                                 owning_lib => {},
1852                                 id => {}
1853                         },
1854                         sdist => {
1855                                 label => {},
1856                                 owning_lib => {},
1857                         },
1858                         sunit => {
1859                                 date_expected => {},
1860                         }
1861                   }
1862                 }
1863         )->gather(1);
1864
1865
1866         for my $ssub (@$ssubs) {
1867                 next unless (@{$ssub->distributions} or @{$ssub->issuances} or @{$ssub->scaps});
1868
1869                 # We don't want O:A:S:unAPI::ssub to return the record, we've got that already
1870                 # In the context of BibTemplate, copies aren't necessary because we pull those
1871                 # in a separate call
1872         $client->respond(
1873             OpenILS::Application::SuperCat::unAPI::ssub
1874                 ->new( $ssub )
1875                 ->as_xml( {no_record => 1, no_items => ($flesh ? 0 : 1)} )
1876         );
1877         }
1878
1879
1880         return "</subscriptions></holdings>\n";
1881 }
1882 __PACKAGE__->register_method(
1883         method    => 'new_record_holdings',
1884         api_name  => 'open-ils.supercat.record.holdings_xml.retrieve',
1885         api_level => 1,
1886         argc      => 1,
1887         stream    => 1,
1888         signature =>
1889                 { desc     => <<"                 DESC",
1890 Returns the XML representation of the requested bibliographic record's holdings
1891                   DESC
1892                   params   =>
1893                         [
1894                                 { name => 'bibId',
1895                                   desc => 'An OpenILS biblio::record_entry ID',
1896                                   type => 'number' },
1897                                 { name => 'orgUnit',
1898                                   desc => 'An OpenILS actor::org_unit short name that limits the scope of returned holdings',
1899                                   type => 'text' },
1900                                 { name => 'depth',
1901                                   desc => 'An OpenILS actor::org_unit_type depththat limits the scope of returned holdings',
1902                                   type => 'number' },
1903                                 { name => 'hideCopies',
1904                                   desc => 'Flag that prevents the inclusion of copies in the returned holdings',
1905                                   type => 'boolean' },
1906                                 { name => 'paging',
1907                                   desc => 'Arry of limit and offset for holdings paging',
1908                                   type => 'array' },
1909                         ],
1910                   'return' =>
1911                         { desc => 'Stream of bib record holdings hierarchy in XML',
1912                           type => 'string' }
1913                 }
1914 );
1915
1916 sub isbn_holdings {
1917         my $self = shift;
1918         my $client = shift;
1919         my $isbn = shift;
1920
1921         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1922
1923         my $recs = $_storage->request(
1924                         'open-ils.cstore.direct.metabib.full_rec.search.atomic',
1925                         { tag => { like => '02%'}, value => {like => "$isbn\%"}}
1926         )->gather(1);
1927
1928         return undef unless (@$recs);
1929
1930         return ($self->method_lookup( 'open-ils.supercat.record.holdings_xml.retrieve')->run( $recs->[0]->record ))[0];
1931 }
1932 __PACKAGE__->register_method(
1933         method    => 'isbn_holdings',
1934         api_name  => 'open-ils.supercat.isbn.holdings_xml.retrieve',
1935         api_level => 1,
1936         argc      => 1,
1937         signature =>
1938                 { desc     => <<"                 DESC",
1939 Returns the XML representation of the requested bibliographic record's holdings
1940                   DESC
1941                   params   =>
1942                         [
1943                                 { name => 'isbn',
1944                                   desc => 'An isbn',
1945                                   type => 'string' },
1946                         ],
1947                   'return' =>
1948                         { desc => 'The bib record holdings hierarchy in XML',
1949                           type => 'string' }
1950                 }
1951 );
1952
1953 sub escape {
1954         my $self = shift;
1955         my $text = shift;
1956     return '' unless $text;
1957         $text =~ s/&/&amp;/gsom;
1958         $text =~ s/</&lt;/gsom;
1959         $text =~ s/>/&gt;/gsom;
1960         $text =~ s/"/&quot;/gsom;
1961         $text =~ s/'/&apos;/gsom;
1962         return $text;
1963 }
1964
1965 sub recent_changes {
1966         my $self = shift;
1967         my $client = shift;
1968         my $when = shift || '1-01-01';
1969         my $limit = shift;
1970
1971         my $type = 'biblio';
1972         $type = 'authority' if ($self->api_name =~ /authority/o);
1973
1974         my $axis = 'create_date';
1975         $axis = 'edit_date' if ($self->api_name =~ /edit/o);
1976
1977         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1978
1979         return $_storage->request(
1980                 "open-ils.cstore.direct.$type.record_entry.id_list.atomic",
1981                 { $axis => { ">" => $when }, id => { '>' => 0 } },
1982                 { order_by => { bre => "$axis desc" }, limit => $limit }
1983         )->gather(1);
1984 }
1985
1986 for my $t ( qw/biblio authority/ ) {
1987         for my $a ( qw/import edit/ ) {
1988
1989                 __PACKAGE__->register_method(
1990                         method    => 'recent_changes',
1991                         api_name  => "open-ils.supercat.$t.record.$a.recent",
1992                         api_level => 1,
1993                         argc      => 0,
1994                         signature =>
1995                                 { desc     => "Returns a list of recently ${a}ed $t records",
1996                                   params   =>
1997                                         [
1998                                                 { name => 'when',
1999                                                   desc => "Date to start looking for ${a}ed records",
2000                                                   default => '1-01-01',
2001                                                   type => 'string' },
2002
2003                                                 { name => 'limit',
2004                                                   desc => "Maximum count to retrieve",
2005                                                   type => 'number' },
2006                                         ],
2007                                   'return' =>
2008                                         { desc => "An id list of $t records",
2009                                           type => 'array' }
2010                                 },
2011                 );
2012         }
2013 }
2014
2015
2016 sub retrieve_authority_marcxml {
2017         my $self = shift;
2018         my $client = shift;
2019         my $rid = shift;
2020
2021         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2022
2023         my $record = $_storage->request( 'open-ils.cstore.direct.authority.record_entry.retrieve' => $rid )->gather(1);
2024         return $U->entityize( $record->marc ) if ($record);
2025         return undef;
2026 }
2027
2028 __PACKAGE__->register_method(
2029         method    => 'retrieve_authority_marcxml',
2030         api_name  => 'open-ils.supercat.authority.marcxml.retrieve',
2031         api_level => 1,
2032         argc      => 1,
2033         signature =>
2034                 { desc     => <<"                 DESC",
2035 Returns the MARCXML representation of the requested authority record
2036                   DESC
2037                   params   =>
2038                         [
2039                                 { name => 'authorityId',
2040                                   desc => 'An OpenILS authority::record_entry id',
2041                                   type => 'number' },
2042                         ],
2043                   'return' =>
2044                         { desc => 'The authority record in MARCXML',
2045                           type => 'string' }
2046                 }
2047 );
2048
2049 sub retrieve_record_marcxml {
2050         my $self = shift;
2051         my $client = shift;
2052         my $rid = shift;
2053
2054         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2055
2056         my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rid )->gather(1);
2057         return $U->entityize( $record->marc ) if ($record);
2058         return undef;
2059 }
2060
2061 __PACKAGE__->register_method(
2062         method    => 'retrieve_record_marcxml',
2063         api_name  => 'open-ils.supercat.record.marcxml.retrieve',
2064         api_level => 1,
2065         argc      => 1,
2066         signature =>
2067                 { desc     => <<"                 DESC",
2068 Returns the MARCXML representation of the requested bibliographic record
2069                   DESC
2070                   params   =>
2071                         [
2072                                 { name => 'bibId',
2073                                   desc => 'An OpenILS biblio::record_entry id',
2074                                   type => 'number' },
2075                         ],
2076                   'return' =>
2077                         { desc => 'The bib record in MARCXML',
2078                           type => 'string' }
2079                 }
2080 );
2081
2082 sub retrieve_isbn_marcxml {
2083         my $self = shift;
2084         my $client = shift;
2085         my $isbn = shift;
2086
2087         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2088
2089         my $recs = $_storage->request(
2090                         'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2091                         { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2092         )->gather(1);
2093
2094         return undef unless (@$recs);
2095
2096         my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
2097         return $U->entityize( $record->marc ) if ($record);
2098         return undef;
2099 }
2100
2101 __PACKAGE__->register_method(
2102         method    => 'retrieve_isbn_marcxml',
2103         api_name  => 'open-ils.supercat.isbn.marcxml.retrieve',
2104         api_level => 1,
2105         argc      => 1,
2106         signature =>
2107                 { desc     => <<"                 DESC",
2108 Returns the MARCXML representation of the requested ISBN
2109                   DESC
2110                   params   =>
2111                         [
2112                                 { name => 'ISBN',
2113                                   desc => 'An ... um ... ISBN',
2114                                   type => 'string' },
2115                         ],
2116                   'return' =>
2117                         { desc => 'The bib record in MARCXML',
2118                           type => 'string' }
2119                 }
2120 );
2121
2122 sub retrieve_record_transform {
2123         my $self = shift;
2124         my $client = shift;
2125         my $rid = shift;
2126
2127         (my $transform = $self->api_name) =~ s/^.+record\.([^\.]+)\.retrieve$/$1/o;
2128
2129         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2130         #$_storage->connect;
2131
2132         my $record = $_storage->request(
2133                 'open-ils.cstore.direct.biblio.record_entry.retrieve',
2134                 $rid
2135         )->gather(1);
2136
2137         return undef unless ($record);
2138
2139         return $U->entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $record->marc ) )->toString);
2140 }
2141
2142 sub retrieve_isbn_transform {
2143         my $self = shift;
2144         my $client = shift;
2145         my $isbn = shift;
2146
2147         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2148
2149         my $recs = $_storage->request(
2150                         'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2151                         { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2152         )->gather(1);
2153
2154         return undef unless (@$recs);
2155
2156         (my $transform = $self->api_name) =~ s/^.+isbn\.([^\.]+)\.retrieve$/$1/o;
2157
2158         my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
2159
2160         return undef unless ($record);
2161
2162         return $U->entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $record->marc ) )->toString);
2163 }
2164
2165 sub retrieve_record_objects {
2166         my $self = shift;
2167         my $client = shift;
2168         my $ids = shift;
2169
2170         $ids = [$ids] unless (ref $ids);
2171         $ids = [grep {$_} @$ids];
2172
2173         return [] unless (@$ids);
2174
2175         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2176         return $_storage->request('open-ils.cstore.direct.biblio.record_entry.search.atomic' => { id => [grep {$_} @$ids] })->gather(1);
2177 }
2178 __PACKAGE__->register_method(
2179         method    => 'retrieve_record_objects',
2180         api_name  => 'open-ils.supercat.record.object.retrieve',
2181         api_level => 1,
2182         argc      => 1,
2183         signature =>
2184                 { desc     => <<"                 DESC",
2185 Returns the Fieldmapper object representation of the requested bibliographic records
2186                   DESC
2187                   params   =>
2188                         [
2189                                 { name => 'bibIds',
2190                                   desc => 'OpenILS biblio::record_entry ids',
2191                                   type => 'array' },
2192                         ],
2193                   'return' =>
2194                         { desc => 'The bib records',
2195                           type => 'array' }
2196                 }
2197 );
2198
2199
2200 sub retrieve_isbn_object {
2201         my $self = shift;
2202         my $client = shift;
2203         my $isbn = shift;
2204
2205         return undef unless ($isbn);
2206
2207         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2208         my $recs = $_storage->request(
2209                         'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2210                         { tag => { like => '02%'}, value => {like => "$isbn\%"}}
2211         )->gather(1);
2212
2213         return undef unless (@$recs);
2214
2215         return $_storage->request(
2216                 'open-ils.cstore.direct.biblio.record_entry.search.atomic',
2217                 { id => $recs->[0]->record }
2218         )->gather(1);
2219 }
2220 __PACKAGE__->register_method(
2221         method    => 'retrieve_isbn_object',
2222         api_name  => 'open-ils.supercat.isbn.object.retrieve',
2223         api_level => 1,
2224         argc      => 1,
2225         signature =>
2226                 { desc     => <<"                 DESC",
2227 Returns the Fieldmapper object representation of the requested bibliographic record
2228                   DESC
2229                   params   =>
2230                         [
2231                                 { name => 'isbn',
2232                                   desc => 'an ISBN',
2233                                   type => 'string' },
2234                         ],
2235                   'return' =>
2236                         { desc => 'The bib record',
2237                           type => 'object' }
2238                 }
2239 );
2240
2241
2242
2243 sub retrieve_metarecord_mods {
2244         my $self = shift;
2245         my $client = shift;
2246         my $rid = shift;
2247
2248         my $_storage = OpenSRF::AppSession->connect( 'open-ils.cstore' );
2249
2250         # Get the metarecord in question
2251         my $mr =
2252         $_storage->request(
2253                 'open-ils.cstore.direct.metabib.metarecord.retrieve' => $rid
2254         )->gather(1);
2255
2256         # Now get the map of all bib records for the metarecord
2257         my $recs =
2258         $_storage->request(
2259                 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
2260                 {metarecord => $rid}
2261         )->gather(1);
2262
2263         $logger->debug("Adding ".scalar(@$recs)." bib record to the MODS of the metarecord");
2264
2265         # and retrieve the lead (master) record as MODS
2266         my ($master) =
2267                 $self   ->method_lookup('open-ils.supercat.record.mods.retrieve')
2268                         ->run($mr->master_record);
2269         my $master_mods = $_parser->parse_string($master)->documentElement;
2270         $master_mods->setNamespace( "http://www.loc.gov/mods/", "mods" );
2271         $master_mods->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2272
2273         # ... and a MODS clone to populate, with guts removed.
2274         my $mods = $_parser->parse_string($master)->documentElement;
2275         $mods->setNamespace( "http://www.loc.gov/mods/", "mods" ); # modsCollection element
2276         $mods->setNamespace('http://www.loc.gov/mods/', undef, 1);
2277         ($mods) = $mods->findnodes('//mods:mods');
2278         #$mods->setNamespace( "http://www.loc.gov/mods/", "mods" ); # mods element
2279         $mods->removeChildNodes;
2280         $mods->setNamespace('http://www.loc.gov/mods/', undef, 1);
2281
2282         # Add the metarecord ID as a (locally defined) info URI
2283         my $recordInfo = $mods
2284                 ->ownerDocument
2285                 ->createElement("recordInfo");
2286
2287         my $recordIdentifier = $mods
2288                 ->ownerDocument
2289                 ->createElement("recordIdentifier");
2290
2291         my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
2292         $year += 1900;
2293         $month += 1;
2294
2295         my $id = $mr->id;
2296         $recordIdentifier->appendTextNode(
2297                 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:metabib-metarecord/$id", $month, $day)
2298         );
2299
2300         $recordInfo->appendChild($recordIdentifier);
2301         $mods->appendChild($recordInfo);
2302
2303         # Grab the title, author and ISBN for the master record and populate the metarecord
2304         my ($title) = $master_mods->findnodes( './mods:titleInfo[not(@type)]' );
2305         
2306         if ($title) {
2307                 $title->setNamespace( "http://www.loc.gov/mods/", "mods" );
2308                 $title->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2309                 $title = $mods->ownerDocument->importNode($title);
2310                 $mods->appendChild($title);
2311         }
2312
2313         my ($author) = $master_mods->findnodes( './mods:name[mods:role/mods:text[text()="creator"]]' );
2314         if ($author) {
2315                 $author->setNamespace( "http://www.loc.gov/mods/", "mods" );
2316                 $author->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2317                 $author = $mods->ownerDocument->importNode($author);
2318                 $mods->appendChild($author);
2319         }
2320
2321         my ($isbn) = $master_mods->findnodes( './mods:identifier[@type="isbn"]' );
2322         if ($isbn) {
2323                 $isbn->setNamespace( "http://www.loc.gov/mods/", "mods" );
2324                 $isbn->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2325                 $isbn = $mods->ownerDocument->importNode($isbn);
2326                 $mods->appendChild($isbn);
2327         }
2328
2329         # ... and loop over the constituent records
2330         for my $map ( @$recs ) {
2331
2332                 # get the MODS
2333                 my ($rec) =
2334                         $self   ->method_lookup('open-ils.supercat.record.mods.retrieve')
2335                                 ->run($map->source);
2336
2337                 my $part_mods = $_parser->parse_string($rec);
2338                 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", "mods" );
2339                 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2340                 ($part_mods) = $part_mods->findnodes('//mods:mods');
2341
2342                 for my $node ( ($part_mods->findnodes( './mods:subject' )) ) {
2343                         $node->setNamespace( "http://www.loc.gov/mods/", "mods" );
2344                         $node->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2345                         $node = $mods->ownerDocument->importNode($node);
2346                         $mods->appendChild( $node );
2347                 }
2348
2349                 my $relatedItem = $mods
2350                         ->ownerDocument
2351                         ->createElement("relatedItem");
2352
2353                 $relatedItem->setAttribute( type => 'constituent' );
2354
2355                 my $identifier = $mods
2356                         ->ownerDocument
2357                         ->createElement("identifier");
2358
2359                 $identifier->setAttribute( type => 'uri' );
2360
2361                 my $subRecordInfo = $mods
2362                         ->ownerDocument
2363                         ->createElement("recordInfo");
2364
2365                 my $subRecordIdentifier = $mods
2366                         ->ownerDocument
2367                         ->createElement("recordIdentifier");
2368
2369                 my $subid = $map->source;
2370                 $subRecordIdentifier->appendTextNode(
2371                         sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:biblio-record_entry/$subid",
2372                                 $month,
2373                                 $day
2374                         )
2375                 );
2376                 $subRecordInfo->appendChild($subRecordIdentifier);
2377
2378                 $relatedItem->appendChild( $subRecordInfo );
2379
2380                 my ($tor) = $part_mods->findnodes( './mods:typeOfResource' );
2381                 $tor->setNamespace( "http://www.loc.gov/mods/", "mods" );
2382                 $tor->setNamespace( "http://www.loc.gov/mods/", undef, 1 ) if ($tor);
2383                 $tor = $mods->ownerDocument->importNode($tor) if ($tor);
2384                 $relatedItem->appendChild($tor) if ($tor);
2385
2386                 if ( my ($part_isbn) = $part_mods->findnodes( './mods:identifier[@type="isbn"]' ) ) {
2387                         $part_isbn->setNamespace( "http://www.loc.gov/mods/", "mods" );
2388                         $part_isbn->setNamespace( "http://www.loc.gov/mods/", undef, 1 );
2389                         $part_isbn = $mods->ownerDocument->importNode($part_isbn);
2390                         $relatedItem->appendChild( $part_isbn );
2391
2392                         if (!$isbn) {
2393                                 $isbn = $mods->appendChild( $part_isbn->cloneNode(1) );
2394                         }
2395                 }
2396
2397                 $mods->appendChild( $relatedItem );
2398
2399         }
2400
2401         $_storage->disconnect;
2402
2403         return $U->entityize($mods->toString);
2404
2405 }
2406 __PACKAGE__->register_method(
2407         method    => 'retrieve_metarecord_mods',
2408         api_name  => 'open-ils.supercat.metarecord.mods.retrieve',
2409         api_level => 1,
2410         argc      => 1,
2411         signature =>
2412                 { desc     => <<"                 DESC",
2413 Returns the MODS representation of the requested metarecord
2414                   DESC
2415                   params   =>
2416                         [
2417                                 { name => 'metarecordId',
2418                                   desc => 'An OpenILS metabib::metarecord id',
2419                                   type => 'number' },
2420                         ],
2421                   'return' =>
2422                         { desc => 'The metarecord in MODS',
2423                           type => 'string' }
2424                 }
2425 );
2426
2427 sub list_metarecord_formats {
2428         my @list = (
2429                 { mods =>
2430                         { namespace_uri   => 'http://www.loc.gov/mods/',
2431                           docs            => 'http://www.loc.gov/mods/',
2432                           schema_location => 'http://www.loc.gov/standards/mods/mods.xsd',
2433                         }
2434                 }
2435         );
2436
2437         for my $type ( keys %metarecord_xslt ) {
2438                 push @list,
2439                         { $type => 
2440                                 { namespace_uri   => $metarecord_xslt{$type}{namespace_uri},
2441                                   docs            => $metarecord_xslt{$type}{docs},
2442                                   schema_location => $metarecord_xslt{$type}{schema_location},
2443                                 }
2444                         };
2445         }
2446
2447         return \@list;
2448 }
2449 __PACKAGE__->register_method(
2450         method    => 'list_metarecord_formats',
2451         api_name  => 'open-ils.supercat.metarecord.formats',
2452         api_level => 1,
2453         argc      => 0,
2454         signature =>
2455                 { desc     => <<"                 DESC",
2456 Returns the list of valid metarecord formats that supercat understands.
2457                   DESC
2458                   'return' =>
2459                         { desc => 'The format list',
2460                           type => 'array' }
2461                 }
2462 );
2463
2464
2465 sub list_authority_formats {
2466         my @list = (
2467                 { marcxml =>
2468                         { namespace_uri   => 'http://www.loc.gov/MARC21/slim',
2469                           docs            => 'http://www.loc.gov/marcxml/',
2470                           schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
2471                         }
2472                 }
2473         );
2474
2475 #       for my $type ( keys %record_xslt ) {
2476 #               push @list,
2477 #                       { $type => 
2478 #                               { namespace_uri   => $record_xslt{$type}{namespace_uri},
2479 #                                 docs            => $record_xslt{$type}{docs},
2480 #                                 schema_location => $record_xslt{$type}{schema_location},
2481 #                               }
2482 #                       };
2483 #       }
2484 #
2485         return \@list;
2486 }
2487 __PACKAGE__->register_method(
2488         method    => 'list_authority_formats',
2489         api_name  => 'open-ils.supercat.authority.formats',
2490         api_level => 1,
2491         argc      => 0,
2492         signature =>
2493                 { desc     => <<"                 DESC",
2494 Returns the list of valid authority formats that supercat understands.
2495                   DESC
2496                   'return' =>
2497                         { desc => 'The format list',
2498                           type => 'array' }
2499                 }
2500 );
2501
2502 sub list_record_formats {
2503         my @list = (
2504                 { marcxml =>
2505                         { namespace_uri   => 'http://www.loc.gov/MARC21/slim',
2506                           docs            => 'http://www.loc.gov/marcxml/',
2507                           schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
2508                         }
2509                 }
2510         );
2511
2512         for my $type ( keys %record_xslt ) {
2513                 push @list,
2514                         { $type => 
2515                                 { namespace_uri   => $record_xslt{$type}{namespace_uri},
2516                                   docs            => $record_xslt{$type}{docs},
2517                                   schema_location => $record_xslt{$type}{schema_location},
2518                                 }
2519                         };
2520         }
2521
2522         return \@list;
2523 }
2524 __PACKAGE__->register_method(
2525         method    => 'list_record_formats',
2526         api_name  => 'open-ils.supercat.record.formats',
2527         api_level => 1,
2528         argc      => 0,
2529         signature =>
2530                 { desc     => <<"                 DESC",
2531 Returns the list of valid record formats that supercat understands.
2532                   DESC
2533                   'return' =>
2534                         { desc => 'The format list',
2535                           type => 'array' }
2536                 }
2537 );
2538 __PACKAGE__->register_method(
2539         method    => 'list_record_formats',
2540         api_name  => 'open-ils.supercat.isbn.formats',
2541         api_level => 1,
2542         argc      => 0,
2543         signature =>
2544                 { desc     => <<"                 DESC",
2545 Returns the list of valid record formats that supercat understands.
2546                   DESC
2547                   'return' =>
2548                         { desc => 'The format list',
2549                           type => 'array' }
2550                 }
2551 );
2552
2553
2554 sub oISBN {
2555         my $self = shift;
2556         my $client = shift;
2557         my $isbn = shift;
2558
2559         $isbn =~ s/-//gso;
2560
2561         throw OpenSRF::EX::InvalidArg ('I need an ISBN please')
2562                 unless (length($isbn) >= 10);
2563
2564         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
2565
2566         # Create a storage session, since we'll be making muliple requests.
2567         $_storage->connect;
2568
2569         # Find the record that has that ISBN.
2570         my $bibrec = $_storage->request(
2571                 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
2572                 { tag => '020', subfield => 'a', value => { like => lc($isbn).'%'} }
2573         )->gather(1);
2574
2575         # Go away if we don't have one.
2576         return {} unless (@$bibrec);
2577
2578         # Find the metarecord for that bib record.
2579         my $mr = $_storage->request(
2580                 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
2581                 {source => $bibrec->[0]->record}
2582         )->gather(1);
2583
2584         # Find the other records for that metarecord.
2585         my $records = $_storage->request(
2586                 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
2587                 {metarecord => $mr->[0]->metarecord}
2588         )->gather(1);
2589
2590         # Just to be safe.  There's currently no unique constraint on sources...
2591         my %unique_recs = map { ($_->source, 1) } @$records;
2592         my @rec_list = sort keys %unique_recs;
2593
2594         # And now fetch the ISBNs for thos records.
2595         my $recs = [];
2596         push @$recs,
2597                 $_storage->request(
2598                         'open-ils.cstore.direct.metabib.full_rec.search',
2599                         { tag => '020', subfield => 'a', record => $_ }
2600                 )->gather(1) for (@rec_list);
2601
2602         # We're done with the storage server session.
2603         $_storage->disconnect;
2604
2605         # Return the oISBN data structure.  This will be XMLized at a higher layer.
2606         return
2607                 { metarecord => $mr->[0]->metarecord,
2608                   record_list => { map { $_ ? ($_->record, $_->value) : () } @$recs } };
2609
2610 }
2611 __PACKAGE__->register_method(
2612         method    => 'oISBN',
2613         api_name  => 'open-ils.supercat.oisbn',
2614         api_level => 1,
2615         argc      => 1,
2616         signature =>
2617                 { desc     => <<"                 DESC",
2618 Returns the ISBN list for the metarecord of the requested isbn
2619                   DESC
2620                   params   =>
2621                         [
2622                                 { name => 'isbn',
2623                                   desc => 'An ISBN.  Duh.',
2624                                   type => 'string' },
2625                         ],
2626                   'return' =>
2627                         { desc => 'record to isbn map',
2628                           type => 'object' }
2629                 }
2630 );
2631
2632 package OpenILS::Application::SuperCat::unAPI;
2633 use base qw/OpenILS::Application::SuperCat/;
2634
2635 sub as_xml {
2636     die "dummy superclass, use a real class";
2637 }
2638
2639 sub new {
2640     my $class = shift;
2641     my $obj = shift;
2642     return unless ($obj);
2643
2644     $class = ref($class) || $class;
2645
2646     if ($class eq __PACKAGE__) {
2647         return unless (ref($obj));
2648         $class .= '::' . $obj->json_hint;
2649     }
2650
2651     return bless { obj => $obj } => $class;
2652 }
2653
2654 sub obj {
2655     my $self = shift;
2656     return $self->{obj};
2657 }
2658
2659 package OpenILS::Application::SuperCat::unAPI::auri;
2660 use base qw/OpenILS::Application::SuperCat::unAPI/;
2661
2662 sub as_xml {
2663     my $self = shift;
2664     my $args = shift;
2665
2666     my $xml = '      <uri xmlns="http://open-ils.org/spec/holdings/v1" ';
2667     $xml .= 'id="tag:open-ils.org:asset-uri/' . $self->obj->id . '" ';
2668     $xml .= 'use_restriction="' . $self->escape( $self->obj->use_restriction ) . '" ';
2669     $xml .= 'label="' . $self->escape( $self->obj->label ) . '" ';
2670     $xml .= 'href="' . $self->escape( $self->obj->href ) . '">';
2671
2672     if (!$args->{no_volumes}) {
2673         if (ref($self->obj->call_number_maps) && @{ $self->obj->call_number_maps }) {
2674             $xml .= "      <volumes>\n" . join(
2675                 '',
2676                 map {
2677                     OpenILS::Application::SuperCat::unAPI
2678                         ->new( $_->call_number )
2679                         ->as_xml({ %$args, no_uris=>1, no_copies=>1 })
2680                 } @{ $self->obj->call_number_maps }
2681             ) . "      </volumes>\n";
2682
2683         } else {
2684             $xml .= "      <volumes/>\n";
2685         }
2686     }
2687
2688     $xml .= "      </uri>\n";
2689
2690     return $xml;
2691 }
2692
2693 package OpenILS::Application::SuperCat::unAPI::acn;
2694 use base qw/OpenILS::Application::SuperCat::unAPI/;
2695
2696 sub as_xml {
2697     my $self = shift;
2698     my $args = shift;
2699
2700     my $xml = '    <volume xmlns="http://open-ils.org/spec/holdings/v1" ';
2701
2702     $xml .= 'id="tag:open-ils.org:asset-call_number/' . $self->obj->id . '" ';
2703     $xml .= 'lib="' . $self->escape( $self->obj->owning_lib->shortname ) . '" ';
2704     $xml .= 'opac_visible="' . $self->obj->owning_lib->opac_visible . '" ';
2705     $xml .= 'label="' . $self->escape( $self->obj->label ) . '">';
2706     $xml .= "\n";
2707
2708     if (!$args->{no_copies}) {
2709         if (ref($self->obj->copies) && @{ $self->obj->copies }) {
2710             $xml .= "      <copies>\n" . join(
2711                 '',
2712                 map {
2713                     OpenILS::Application::SuperCat::unAPI
2714                         ->new( $_ )
2715                         ->as_xml({ %$args, no_volume=>1 })
2716                 } @{ $self->obj->copies }
2717             ) . "      </copies>\n";
2718
2719         } else {
2720             $xml .= "      <copies/>\n";
2721         }
2722     }
2723
2724     if (!$args->{no_uris}) {
2725         if (ref($self->obj->uri_maps) && @{ $self->obj->uri_maps }) {
2726             $xml .= "      <uris>\n" . join(
2727                 '',
2728                 map {
2729                     OpenILS::Application::SuperCat::unAPI
2730                         ->new( $_->uri )
2731                         ->as_xml({ %$args, no_volumes=>1 })
2732                 } @{ $self->obj->uri_maps }
2733             ) . "      </uris>\n";
2734
2735         } else {
2736             $xml .= "      <uris/>\n";
2737         }
2738     }
2739
2740
2741     $xml .= '      <owning_lib xmlns="http://open-ils.org/spec/actors/v1" ';
2742     $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->owning_lib->id . '" ';
2743     $xml .= 'shortname="'.$self->escape( $self->obj->owning_lib->shortname ) .'" ';
2744     $xml .= 'name="'.$self->escape( $self->obj->owning_lib->name ) .'"/>';
2745     $xml .= "\n";
2746
2747     unless ($args->{no_record}) {
2748         my $rec_tag = "tag:open-ils.org:biblio-record_entry/".$self->obj->record->id.'/'.$self->escape( $self->obj->owning_lib->shortname ) ;
2749
2750         my $r_doc = $parser->parse_string($self->obj->record->marc);
2751         $r_doc->documentElement->setAttribute( id => $rec_tag );
2752         $xml .= $U->entityize($r_doc->documentElement->toString);
2753     }
2754
2755     $xml .= "    </volume>\n";
2756
2757     return $xml;
2758 }
2759
2760 package OpenILS::Application::SuperCat::unAPI::ssub;
2761 use base qw/OpenILS::Application::SuperCat::unAPI/;
2762
2763 sub as_xml {
2764     my $self = shift;
2765     my $args = shift;
2766
2767     my $xml = '    <subscription xmlns="http://open-ils.org/spec/holdings/v1" ';
2768
2769     $xml .= 'id="tag:open-ils.org:serial-subscription/' . $self->obj->id . '" ';
2770     $xml .= 'start="' . $self->escape( $self->obj->start_date ) . '" ';
2771     $xml .= 'end="' . $self->escape( $self->obj->end_date ) . '" ';
2772     $xml .= 'expected_date_offset="' . $self->escape( $self->obj->expected_date_offset ) . '">';
2773     $xml .= "\n";
2774
2775     if (!$args->{no_distributions}) {
2776         if (ref($self->obj->distributions) && @{ $self->obj->distributions }) {
2777             $xml .= "      <distributions>\n" . join(
2778                 '',
2779                 map {
2780                     OpenILS::Application::SuperCat::unAPI
2781                         ->new( $_ )
2782                         ->as_xml({ %$args, no_subscription=>1, no_issuance=>1 })
2783                 } @{ $self->obj->distributions }
2784             ) . "      </distributions>\n";
2785
2786         } else {
2787             $xml .= "      <distributions/>\n";
2788         }
2789     }
2790
2791     if (!$args->{no_captions_and_patterns}) {
2792         if (ref($self->obj->scaps) && @{ $self->obj->scaps }) {
2793             $xml .= "      <captions_and_patterns>\n" . join(
2794                 '',
2795                 map {
2796                     OpenILS::Application::SuperCat::unAPI
2797                         ->new( $_ )
2798                         ->as_xml({ %$args, no_subscription=>1 })
2799                 } @{ $self->obj->scaps }
2800             ) . "      </captions_and_patterns>\n";
2801
2802         } else {
2803             $xml .= "      <captions_and_patterns/>\n";
2804         }
2805     }
2806
2807     if (!$args->{no_issuances}) {
2808         if (ref($self->obj->issuances) && @{ $self->obj->issuances }) {
2809             $xml .= "      <issuances>\n" . join(
2810                 '',
2811                 map {
2812                     OpenILS::Application::SuperCat::unAPI
2813                         ->new( $_ )
2814                         ->as_xml({ %$args, no_subscription=>1, no_items=>1 })
2815                 } @{ $self->obj->issuances }
2816             ) . "      </issuances>\n";
2817
2818         } else {
2819             $xml .= "      <issuances/>\n";
2820         }
2821     }
2822
2823
2824     $xml .= '      <owning_lib xmlns="http://open-ils.org/spec/actors/v1" ';
2825     $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->owning_lib->id . '" ';
2826     $xml .= 'shortname="'.$self->escape( $self->obj->owning_lib->shortname ) .'" ';
2827     $xml .= 'name="'.$self->escape( $self->obj->owning_lib->name ) .'"/>';
2828     $xml .= "\n";
2829
2830     unless ($args->{no_record}) {
2831         my $rec_tag = "tag:open-ils.org:biblio-record_entry/".$self->obj->record->id.'/'.$self->escape( $self->obj->owning_lib->shortname ) ;
2832
2833         my $r_doc = $parser->parse_string($self->obj->record_entry->marc);
2834         $r_doc->documentElement->setAttribute( id => $rec_tag );
2835         $xml .= $U->entityize($r_doc->documentElement->toString);
2836     }
2837
2838     $xml .= "    </subscription>\n";
2839
2840     return $xml;
2841 }
2842
2843 package OpenILS::Application::SuperCat::unAPI::ssum_base;
2844 use base qw/OpenILS::Application::SuperCat::unAPI/;
2845
2846 sub as_xml {
2847     my $self = shift;
2848     my $args = shift;
2849
2850     (my $type = ref($self)) =~ s/^.+([^:]+)$/$1/;
2851
2852     my $xml = "    <serial_summary xmlns=\"http://open-ils.org/spec/holdings/v1\" type=\"$type\" ";
2853
2854     $xml .= "id=\"tag:open-ils.org:serial-summary-$type/" . $self->obj->id . '" ';
2855     $xml .= 'generated_coverage="' . $self->escape( $self->obj->generated_coverage ) . '" ';
2856     $xml .= 'show_generated="' . $self->escape( $self->obj->show_generated ) . '" ';
2857     $xml .= 'textual_holdings="' . $self->escape( $self->obj->textual_holdings ) . '">';
2858     $xml .= "\n";
2859
2860         $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->distribution )->as_xml({ %$args, no_summaries=>1 }) if (!$args->{no_distribution});
2861
2862     $xml .= "    </serial_summary>\n";
2863
2864     return $xml;
2865 }
2866
2867
2868 package OpenILS::Application::SuperCat::unAPI::sssum;
2869 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
2870
2871 package OpenILS::Application::SuperCat::unAPI::sbsum;
2872 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
2873
2874 package OpenILS::Application::SuperCat::unAPI::sisum;
2875 use base qw/OpenILS::Application::SuperCat::unAPI::ssum_base/;
2876
2877 package OpenILS::Application::SuperCat::unAPI::sdist;
2878 use base qw/OpenILS::Application::SuperCat::unAPI/;
2879
2880 sub as_xml {
2881     my $self = shift;
2882     my $args = shift;
2883
2884     my $xml = '    <distribution xmlns="http://open-ils.org/spec/holdings/v1" ';
2885
2886     $xml .= 'id="tag:open-ils.org:serial-distribution/' . $self->obj->id . '" ';
2887     $xml .= 'label="' . $self->escape( $self->obj->label ) . '" ';
2888     $xml .= 'unit_label_prefix="' . $self->escape( $self->obj->unit_label_prefix ) . '" ';
2889     $xml .= 'unit_label_suffix="' . $self->escape( $self->obj->unit_label_suffix ) . '">';
2890     $xml .= "\n";
2891
2892     if (!$args->{no_distributions}) {
2893         if (ref($self->obj->streams) && @{ $self->obj->streams }) {
2894             $xml .= "      <streams>\n" . join(
2895                 '',
2896                 map {
2897                     OpenILS::Application::SuperCat::unAPI
2898                         ->new( $_ )
2899                         ->as_xml({ %$args, no_distribution=>1 })
2900                 } @{ $self->obj->streams }
2901             ) . "      </streams>\n";
2902
2903         } else {
2904             $xml .= "      <streams/>\n";
2905         }
2906     }
2907
2908     if (!$args->{no_summaries}) {
2909         $xml .= "      <summaries>\n";
2910         $xml .= join ('',
2911         map {
2912             defined $_ ?
2913                 OpenILS::Application::SuperCat::unAPI
2914                 ->new( $_ )
2915                 ->as_xml({ %$args, no_distribution=>1 }) : ""
2916         } ($self->obj->basic_summary, $self->obj->supplement_summary, $self->obj->index_summary)
2917         );
2918
2919         $xml .= "      </summaries>\n";
2920     }
2921
2922
2923     $xml .= '      <holding_lib xmlns="http://open-ils.org/spec/actors/v1" ';
2924     $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->holding_lib->id . '" ';
2925     $xml .= 'shortname="'.$self->escape( $self->obj->holding_lib->shortname ) .'" ';
2926     $xml .= 'name="'.$self->escape( $self->obj->holding_lib->name ) .'"/>';
2927     $xml .= "\n";
2928
2929         $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_distributions=>1 }) if (!$args->{no_subscription});
2930
2931     if (!$args->{no_record} && $self->obj->record_entry) {
2932         my $rec_tag = "tag:open-ils.org:serial-record_entry/".$self->obj->record_entry->id ;
2933
2934         my $r_doc = $parser->parse_string($self->obj->record_entry->marc);
2935         $r_doc->documentElement->setAttribute( id => $rec_tag );
2936         $xml .= $U->entityize($r_doc->documentElement->toString);
2937     }
2938
2939     $xml .= "    </distribution>\n";
2940
2941     return $xml;
2942 }
2943
2944 package OpenILS::Application::SuperCat::unAPI::sstr;
2945 use base qw/OpenILS::Application::SuperCat::unAPI/;
2946
2947 sub as_xml {
2948     my $self = shift;
2949     my $args = shift;
2950
2951     my $xml = '    <stream xmlns="http://open-ils.org/spec/holdings/v1" ';
2952
2953     $xml .= 'id="tag:open-ils.org:serial-stream/' . $self->obj->id . '" ';
2954     $xml .= 'routing_label="' . $self->escape( $self->obj->routing_label ) . '">';
2955     $xml .= "\n";
2956
2957     if (!$args->{no_items}) {
2958         if (ref($self->obj->items) && @{ $self->obj->items }) {
2959             $xml .= "      <items>\n" . join(
2960                 '',
2961                 map {
2962                     OpenILS::Application::SuperCat::unAPI
2963                         ->new( $_ )
2964                         ->as_xml({ %$args, no_stream=>1 })
2965                 } @{ $self->obj->items }
2966             ) . "      </items>\n";
2967
2968         } else {
2969             $xml .= "      <items/>\n";
2970         }
2971     }
2972
2973         #XXX routing_list_user's?
2974
2975         $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->distribution )->as_xml({ %$args, no_streams=>1 }) if (!$args->{no_distribution});
2976
2977     $xml .= "    </stream>\n";
2978
2979     return $xml;
2980 }
2981
2982 package OpenILS::Application::SuperCat::unAPI::sitem;
2983 use base qw/OpenILS::Application::SuperCat::unAPI/;
2984
2985 sub as_xml {
2986     my $self = shift;
2987     my $args = shift;
2988
2989     my $xml = '    <serial_item xmlns="http://open-ils.org/spec/holdings/v1" ';
2990
2991     $xml .= 'id="tag:open-ils.org:serial-item/' . $self->obj->id . '" ';
2992     $xml .= 'date_expected="' . $self->escape( $self->obj->date_expected ) . '"';
2993     $xml .= ' date_received="' . $self->escape( $self->obj->date_received ) .'"'if ($self->obj->date_received);
2994
2995         if ($args->{no_issuance}) {
2996                 my $siss = ref($self->obj->issuance) ? $self->obj->issuance->id : $self->obj->issuance;
2997             $xml .= ' issuance="tag:open-ils.org:serial-issuance/' . $siss . '"';
2998         }
2999
3000     $xml .= ">\n";
3001
3002         if (ref($self->obj->notes) && $self->obj->notes) {
3003                 $xml .= "        <notes>\n";
3004                 for my $note ( @{$self->obj->notes} ) {
3005                         next unless ( $note->pub eq 't' );
3006                         $xml .= sprintf('        <note date="%s" title="%s">%s</note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3007                         $xml .= "\n";
3008                 }
3009                 $xml .= "        </notes>\n";
3010     } else {
3011         $xml .= "      <notes/>\n";
3012         }
3013
3014         $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->issuance )->as_xml({ %$args, no_items=>1 }) if (!$args->{no_issuance});
3015         $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->stream )->as_xml({ %$args, no_items=>1 }) if (!$args->{no_stream});
3016         $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->unit )->as_xml({ %$args, no_items=>1, no_volumes=>1 }) if (!$args->{no_unit});
3017         $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->uri )->as_xml({ %$args, no_items=>1, no_volumes=>1 }) if (!$args->{no_uri});
3018
3019     $xml .= "    </stream>\n";
3020
3021     return $xml;
3022 }
3023
3024 package OpenILS::Application::SuperCat::unAPI::sunit;
3025 use base qw/OpenILS::Application::SuperCat::unAPI/;
3026
3027 sub as_xml {
3028     my $self = shift;
3029     my $args = shift;
3030
3031     my $xml = '      <serial_item xmlns="http://open-ils.org/spec/holdings/v1" '.
3032         'id="tag:open-ils.org:serial-unit/' . $self->obj->id . '" ';
3033
3034     $xml .= $_ . '="' . $self->escape( $self->obj->$_  ) . '" ' for (qw/
3035         create_date edit_date copy_number circulate deposit ref holdable deleted
3036         deposit_amount price barcode circ_modifier circ_as_type opac_visible
3037                 status_changed_time floating mint_condition label label_sort_key contents
3038     /);
3039
3040     $xml .= ">\n";
3041
3042     $xml .= '        <status ident="' . $self->obj->status->id . '">' . $self->escape( $self->obj->status->name  ) . "</status>\n";
3043     $xml .= '        <location ident="' . $self->obj->location->id . '">' . $self->escape( $self->obj->location->name  ) . "</location>\n";
3044     $xml .= '        <circlib ident="' . $self->obj->circ_lib->id . '">' . $self->escape( $self->obj->circ_lib->name  ) . "</circlib>\n";
3045
3046     $xml .= '        <circ_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3047     $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->circ_lib->id . '" ';
3048     $xml .= 'shortname="'.$self->escape( $self->obj->circ_lib->shortname ) .'" ';
3049     $xml .= 'name="'.$self->escape( $self->obj->circ_lib->name ) .'"/>';
3050     $xml .= "\n";
3051
3052         $xml .= "        <copy_notes>\n";
3053         if (ref($self->obj->notes) && $self->obj->notes) {
3054                 for my $note ( @{$self->obj->notes} ) {
3055                         next unless ( $note->pub eq 't' );
3056                         $xml .= sprintf('        <copy_note date="%s" title="%s">%s</copy_note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3057                         $xml .= "\n";
3058                 }
3059         }
3060
3061         $xml .= "        </copy_notes>\n";
3062     $xml .= "        <statcats>\n";
3063
3064         if (ref($self->obj->stat_cat_entries) && $self->obj->stat_cat_entries) {
3065                 for my $sce ( @{$self->obj->stat_cat_entries} ) {
3066                         next unless ( $sce->stat_cat->opac_visible eq 't' );
3067                         $xml .= sprintf('          <statcat name="%s">%s</statcat>',$self->escape($sce->stat_cat->name) ,$self->escape($sce->value));
3068                         $xml .= "\n";
3069                 }
3070         }
3071         $xml .= "        </statcats>\n";
3072
3073     unless ($args->{no_volume}) {
3074         if (ref($self->obj->call_number)) {
3075             $xml .= OpenILS::Application::SuperCat::unAPI
3076                         ->new( $self->obj->call_number )
3077                         ->as_xml({ %$args, no_copies=>1 });
3078         } else {
3079             $xml .= "    <volume/>\n";
3080         }
3081     }
3082
3083     $xml .= "      </serial_item>\n";
3084
3085     return $xml;
3086 }
3087
3088 package OpenILS::Application::SuperCat::unAPI::scap;
3089 use base qw/OpenILS::Application::SuperCat::unAPI/;
3090
3091 sub as_xml {
3092     my $self = shift;
3093     my $args = shift;
3094
3095     my $xml = '      <caption_and_pattern xmlns="http://open-ils.org/spec/holdings/v1" '.
3096         'id="tag:open-ils.org:serial-caption_and_pattern/' . $self->obj->id . '" ';
3097
3098     $xml .= $_ . '="' . $self->escape( $self->obj->$_  ) . '" ' for (qw/
3099         create_date type active pattern_code enum_1 enum_2 enum_3 enum_4
3100                 enum_5 enum_6 chron_1 chron_2 chron_3 chron_4 chron_5 start_date end_date
3101     /);
3102     $xml .= ">\n";
3103         $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_captions_and_patterns=>1 }) if (!$args->{no_subscription});
3104     $xml .= "      </caption_and_pattern>\n";
3105
3106     return $xml;
3107 }
3108
3109 package OpenILS::Application::SuperCat::unAPI::siss;
3110 use base qw/OpenILS::Application::SuperCat::unAPI/;
3111
3112 sub as_xml {
3113     my $self = shift;
3114     my $args = shift;
3115
3116     my $xml = '      <issuance xmlns="http://open-ils.org/spec/holdings/v1" '.
3117         'id="tag:open-ils.org:serial-issuance/' . $self->obj->id . '" ';
3118
3119     $xml .= $_ . '="' . $self->escape( $self->obj->$_  ) . '" '
3120                 for (qw/create_date edit_date label date_published holding_code holding_type holding_link_id/);
3121
3122     $xml .= ">\n";
3123
3124     if (!$args->{no_items}) {
3125         if (ref($self->obj->items) && @{ $self->obj->items }) {
3126             $xml .= "      <items>\n" . join(
3127                 '',
3128                 map {
3129                     OpenILS::Application::SuperCat::unAPI
3130                         ->new( $_ )
3131                         ->as_xml({ %$args, no_stream=>1 })
3132                 } @{ $self->obj->items }
3133             ) . "      </items>\n";
3134
3135         } else {
3136             $xml .= "      <items/>\n";
3137         }
3138     }
3139
3140         $xml .= OpenILS::Application::SuperCat::unAPI->new( $self->obj->subscription )->as_xml({ %$args, no_issuances=>1 }) if (!$args->{no_subscription});
3141     $xml .= "      </issuance>\n";
3142
3143     return $xml;
3144 }
3145
3146 package OpenILS::Application::SuperCat::unAPI::acp;
3147 use base qw/OpenILS::Application::SuperCat::unAPI/;
3148
3149 sub as_xml {
3150     my $self = shift;
3151     my $args = shift;
3152
3153     my $xml = '      <copy xmlns="http://open-ils.org/spec/holdings/v1" '.
3154         'id="tag:open-ils.org:asset-copy/' . $self->obj->id . '" ';
3155
3156     $xml .= $_ . '="' . $self->escape( $self->obj->$_  ) . '" ' for (qw/
3157         create_date edit_date copy_number circulate deposit ref holdable deleted
3158         deposit_amount price barcode circ_modifier circ_as_type opac_visible
3159     /);
3160
3161     $xml .= ">\n";
3162
3163     $xml .= '        <status ident="' . $self->obj->status->id . '">' . $self->escape( $self->obj->status->name  ) . "</status>\n";
3164     $xml .= '        <location ident="' . $self->obj->location->id . '" opac_visible="'.$self->obj->location->opac_visible.'">' . $self->escape( $self->obj->location->name  ) . "</location>\n";
3165     $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";
3166
3167     $xml .= '        <circ_lib xmlns="http://open-ils.org/spec/actors/v1" ';
3168     $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->circ_lib->id . '" ';
3169     $xml .= 'shortname="'.$self->escape( $self->obj->circ_lib->shortname ) .'" ';
3170     $xml .= 'name="'.$self->escape( $self->obj->circ_lib->name ) .'" opac_visible="'.$self->obj->circ_lib->opac_visible.'"/>';
3171     $xml .= "\n";
3172
3173         $xml .= "        <copy_notes>\n";
3174         if (ref($self->obj->notes) && $self->obj->notes) {
3175                 for my $note ( @{$self->obj->notes} ) {
3176                         next unless ( $note->pub eq 't' );
3177                         $xml .= sprintf('        <copy_note date="%s" title="%s">%s</copy_note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
3178                         $xml .= "\n";
3179                 }
3180         }
3181
3182         $xml .= "        </copy_notes>\n";
3183     $xml .= "        <statcats>\n";
3184
3185         if (ref($self->obj->stat_cat_entries) && $self->obj->stat_cat_entries) {
3186                 for my $sce ( @{$self->obj->stat_cat_entries} ) {
3187                         next unless ( $sce->stat_cat->opac_visible eq 't' );
3188                         $xml .= sprintf('          <statcat name="%s">%s</statcat>',$self->escape($sce->stat_cat->name) ,$self->escape($sce->value));
3189                         $xml .= "\n";
3190                 }
3191         }
3192         $xml .= "        </statcats>\n";
3193
3194     unless ($args->{no_volume}) {
3195         if (ref($self->obj->call_number)) {
3196             $xml .= OpenILS::Application::SuperCat::unAPI
3197                         ->new( $self->obj->call_number )
3198                         ->as_xml({ %$args, no_copies=>1 });
3199         } else {
3200             $xml .= "    <volume/>\n";
3201         }
3202     }
3203
3204     $xml .= "      </copy>\n";
3205
3206     return $xml;
3207 }
3208
3209
3210 1;
3211 # vim: noet:ts=4:sw=4