]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/SuperCat.pm
documentation cut-n-pasto ... authority records are not org-scoped
[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
248         my ($before_limit,$after_limit) = (0,0);
249         my ($before_offset,$after_offset) = (0,0);
250
251         if (!$page) {
252                 $before_limit = $after_limit = int($page_size / 2);
253                 $after_limit += 1 if ($page_size % 2);
254         } else {
255                 $before_offset = $after_offset = int($page_size / 2);
256                 $before_offset += 1 if ($page_size % 2);
257                 $before_limit = $after_limit = $page_size;
258         }
259
260         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
261
262         my $o_search = { shortname => $ou };
263         if (!$ou || $ou eq '-') {
264                 $o_search = { parent_ou => undef };
265         }
266
267         my $orgs = $_storage->request(
268                 "open-ils.cstore.direct.actor.org_unit.search",
269                 $o_search,
270                 { flesh         => 100,
271                   flesh_fields  => { aou        => [qw/children/] }
272                 }
273         )->gather(1);
274
275         my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
276
277         $logger->debug("Searching for CNs at orgs [".join(',',@ou_ids)."], based on $ou");
278
279         my @list = ();
280
281         if ($page <= 0) {
282                 my $before = $_storage->request(
283                         "open-ils.cstore.direct.asset.call_number.search.atomic",
284                         { label         => { "<" => { transform => "upper", value => ["upper", $label] } },
285                           owning_lib    => \@ou_ids,
286               deleted => 'f',
287                         },
288                         { flesh         => 1,
289                           flesh_fields  => { acn => [qw/record owning_lib/] },
290                           order_by      => { acn => "upper(label) desc, id desc, owning_lib desc" },
291                           limit         => $before_limit,
292                           offset        => abs($page) * $page_size - $before_offset,
293                         }
294                 )->gather(1);
295                 push @list, reverse(@$before);
296         }
297
298         if ($page >= 0) {
299                 my $after = $_storage->request(
300                         "open-ils.cstore.direct.asset.call_number.search.atomic",
301                         { label         => { ">=" => { transform => "upper", value => ["upper", $label] } },
302                           owning_lib    => \@ou_ids,
303               deleted => 'f',
304                         },
305                         { flesh         => 1,
306                           flesh_fields  => { acn => [qw/record owning_lib/] },
307                           order_by      => { acn => "upper(label), id, owning_lib" },
308                           limit         => $after_limit,
309                           offset        => abs($page) * $page_size - $after_offset,
310                         }
311                 )->gather(1);
312                 push @list, @$after;
313         }
314
315         return \@list;
316 }
317 __PACKAGE__->register_method(
318         method    => 'cn_browse',
319         api_name  => 'open-ils.supercat.call_number.browse',
320         api_level => 1,
321         argc      => 1,
322         signature =>
323                 { desc     => <<"                 DESC",
324 Returns the XML representation of the requested bibliographic record's holdings
325                   DESC
326                   params   =>
327                         [
328                                 { name => 'label',
329                                   desc => 'The target call number lable',
330                                   type => 'string' },
331                                 { name => 'org_unit',
332                                   desc => 'The org unit shortname (or "-" or undef for global) to browse',
333                                   type => 'string' },
334                                 { name => 'page_size',
335                                   desc => 'Count of call numbers to retrieve, default is 9',
336                                   type => 'number' },
337                                 { name => 'page',
338                                   desc => 'The page of call numbers to retrieve, calculated based on page_size.  Can be positive, negative or 0.',
339                                   type => 'number' },
340                         ],
341                   'return' =>
342                         { desc => 'Call numbers with owning_lib and record fleshed',
343                           type => 'array' }
344                 }
345 );
346
347
348 sub new_books_by_item {
349         my $self = shift;
350         my $client = shift;
351
352         my $ou = shift;
353         my $page_size = shift || 10;
354         my $page = shift || 1;
355
356     my $offset = $page_size * ($page - 1);
357
358         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
359
360         my @ou_ids;
361         if ($ou && $ou ne '-') {
362                 my $orgs = $_storage->request(
363                         "open-ils.cstore.direct.actor.org_unit.search",
364                         { shortname => $ou },
365                         { flesh         => 100,
366                           flesh_fields  => { aou        => [qw/children/] }
367                         }
368                 )->gather(1);
369                 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
370         }
371
372         $logger->debug("Searching for records with new copies at orgs [".join(',',@ou_ids)."], based on $ou");
373         my $cns = $_storage->request(
374                 "open-ils.cstore.json_query.atomic",
375                 { select        => { acn => ['record'],
376                          acp => [{ aggregate => 1 => transform => max => column => create_date => alias => 'create_date'}]
377                        },
378                   from          => { 'acn' => { 'acp' => { field => call_number => fkey => 'id' } } },
379                   where         =>
380                         { '+acp' => { deleted => 'f', (@ou_ids) ? ( circ_lib => \@ou_ids) : () },
381                           '+acn' => { record => { '>' => 0 } },
382                         }, 
383                   order_by      => { acp => { create_date => { transform => 'max', direction => 'desc' } } },
384                   limit         => $page_size,
385                   offset        => $offset
386                 }
387         )->gather(1);
388
389         return [ map { $_->{record} } @$cns ];
390 }
391 __PACKAGE__->register_method(
392         method    => 'new_books_by_item',
393         api_name  => 'open-ils.supercat.new_book_list',
394         api_level => 1,
395         argc      => 1,
396         signature =>
397                 { desc     => <<"                 DESC",
398 Returns the XML representation of the requested bibliographic record's holdings
399                   DESC
400                   params   =>
401                         [
402                                 { name => 'org_unit',
403                                   desc => 'The org unit shortname (or "-" or undef for global) to list',
404                                   type => 'string' },
405                                 { name => 'page_size',
406                                   desc => 'Count of records to retrieve, default is 10',
407                                   type => 'number' },
408                                 { name => 'page',
409                                   desc => 'The page of records to retrieve, calculated based on page_size.  Starts at 1.',
410                                   type => 'number' },
411                         ],
412                   'return' =>
413                         { desc => 'Record IDs',
414                           type => 'array' }
415                 }
416 );
417
418
419 sub general_browse {
420         my $self = shift;
421         my $client = shift;
422     return tag_sf_browse($self, $client, $self->{tag}, $self->{subfield}, @_);
423 }
424 __PACKAGE__->register_method(
425         method    => 'general_browse',
426         api_name  => 'open-ils.supercat.title.browse',
427         tag       => 'tnf', subfield => 'a',
428         api_level => 1,
429         argc      => 1,
430         signature =>
431                 { desc     => "Returns a list of the requested org-scoped record ids held",
432                   params   =>
433                         [ { name => 'value', desc => 'The target title', type => 'string' },
434                           { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
435                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
436                           { name => 'page', desc => 'The page of records retrieve, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
437                   'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
438                 }
439 );
440 __PACKAGE__->register_method(
441         method    => 'general_browse',
442         api_name  => 'open-ils.supercat.author.browse',
443         tag       => [qw/100 110 111/], subfield => 'a',
444         api_level => 1,
445         argc      => 1,
446         signature =>
447                 { desc     => "Returns a list of the requested org-scoped record ids held",
448                   params   =>
449                         [ { name => 'value', desc => 'The target author', type => 'string' },
450                           { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
451                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
452                           { name => 'page', desc => 'The page of records retrieve, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
453                   'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
454                 }
455 );
456 __PACKAGE__->register_method(
457         method    => 'general_browse',
458         api_name  => 'open-ils.supercat.subject.browse',
459         tag       => [qw/600 610 611 630 648 650 651 653 655 656 662 690 691 696 697 698 699/], subfield => 'a',
460         api_level => 1,
461         argc      => 1,
462         signature =>
463                 { desc     => "Returns a list of the requested org-scoped record ids held",
464                   params   =>
465                         [ { name => 'value', desc => 'The target subject', type => 'string' },
466                           { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
467                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
468                           { name => 'page', desc => 'The page of records retrieve, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
469                   'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
470                 }
471 );
472 __PACKAGE__->register_method(
473         method    => 'general_browse',
474         api_name  => 'open-ils.supercat.topic.browse',
475         tag       => [qw/650 690/], subfield => 'a',
476         api_level => 1,
477         argc      => 1,
478         signature =>
479                 { desc     => "Returns a list of the requested org-scoped record ids held",
480                   params   =>
481                         [ { name => 'value', desc => 'The target topical subject', type => 'string' },
482                           { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
483                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
484                           { name => 'page', desc => 'The page of records retrieve, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
485                   'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
486                 }
487 );
488 __PACKAGE__->register_method(
489         method    => 'general_browse',
490         api_name  => 'open-ils.supercat.series.browse',
491         tag       => [qw/440 490 800 810 811 830/], subfield => 'a',
492         api_level => 1,
493         argc      => 1,
494         signature =>
495                 { desc     => "Returns a list of the requested org-scoped record ids held",
496                   params   =>
497                         [ { name => 'value', desc => 'The target series', type => 'string' },
498                           { name => 'org_unit', desc => 'The org unit shortname (or "-" or undef for global) to browse', type => 'string' },
499                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
500                           { name => 'page', desc => 'The page of records retrieve, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
501                   'return' => { desc => 'Record IDs that have copies at the relevant org units', type => 'array' }
502                 }
503 );
504
505
506 sub tag_sf_browse {
507         my $self = shift;
508         my $client = shift;
509
510         my $tag = shift;
511         my $subfield = shift;
512         my $value = shift;
513         my $ou = shift;
514         my $page_size = shift || 9;
515         my $page = shift || 0;
516
517         my ($before_limit,$after_limit) = (0,0);
518         my ($before_offset,$after_offset) = (0,0);
519
520         if (!$page) {
521                 $before_limit = $after_limit = int($page_size / 2);
522                 $after_limit += 1 if ($page_size % 2);
523         } else {
524                 $before_offset = $after_offset = int($page_size / 2);
525                 $before_offset += 1 if ($page_size % 2);
526                 $before_limit = $after_limit = $page_size;
527         }
528
529         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
530
531         my @ou_ids;
532         if ($ou && $ou ne '-') {
533                 my $orgs = $_storage->request(
534                         "open-ils.cstore.direct.actor.org_unit.search",
535                         { shortname => $ou },
536                         { flesh         => 100,
537                           flesh_fields  => { aou        => [qw/children/] }
538                         }
539                 )->gather(1);
540                 @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
541         }
542
543         $logger->debug("Searching for records at orgs [".join(',',@ou_ids)."], based on $ou");
544
545         my @list = ();
546
547         if ($page <= 0) {
548                 my $before = $_storage->request(
549                         "open-ils.cstore.json_query.atomic",
550                         { select        => { mfr => [qw/record value/] },
551                           from          => 'mfr',
552                           where         =>
553                                 { '+mfr'        =>
554                                         { tag   => $tag,
555                                           subfield => $subfield,
556                                           value => { '<' => lc($value) }
557                                         },
558                   '-or' => [
559                                 { '-exists'     =>
560                                         { select=> { acp => [ 'id' ] },
561                                           from  => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
562                                               where     =>
563                                                 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
564                                                   '+acp' => { deleted => 'f', (@ou_ids) ? ( circ_lib => \@ou_ids) : () }
565                                                 },
566                                           limit => 1
567                                         }
568                     },
569                     { '-exists' =>
570                                         { select=> { auri => [ 'id' ] },
571                                           from  => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
572                                           where =>
573                                                 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
574                                                   '+auri' => { active => 't' }
575                                                 },
576                                           limit => 1
577                                         }
578                     }
579                   ]
580                                 }, 
581                           order_by      => { mfr => { value => 'desc' } },
582                           limit         => $before_limit,
583                           offset        => abs($page) * $page_size - $before_offset,
584                         }
585                 )->gather(1);
586                 push @list, map { $_->{record} } reverse(@$before);
587         }
588
589         if ($page >= 0) {
590                 my $after = $_storage->request(
591                         "open-ils.cstore.json_query.atomic",
592                         { select        => { mfr => [qw/record value/] },
593                           from          => 'mfr',
594                           where         =>
595                                 { '+mfr'        =>
596                                         { tag   => $tag,
597                                           subfield => $subfield,
598                                           value => { '>=' => lc($value) }
599                                         },
600                                   '-or' => [
601                     { '-exists' =>
602                                         { select=> { acp => [ 'id' ] },
603                                           from  => { acn => { acp => { field => 'call_number', fkey => 'id' } } },
604                                           where =>
605                                                 { '+acn' => { record => { '=' => { '+mfr' => 'record' } } },
606                                                   '+acp' => { deleted => 'f', (@ou_ids) ? ( circ_lib => \@ou_ids) : () }
607                                                 },
608                                           limit => 1
609                                         }
610                     },
611                     { '-exists' =>
612                                         { select=> { auri => [ 'id' ] },
613                                           from  => { acn => { auricnm => { field => 'call_number', fkey => 'id', join => { auri => { field => 'id', fkey => 'uri' } } } } },
614                                           where =>
615                                                 { '+acn' => { record => { '=' => { '+mfr' => 'record' } }, (@ou_ids) ? ( owning_lib => \@ou_ids) : () },
616                                                   '+auri' => { active => 't' }
617                                                 },
618                                           limit => 1
619                                         },
620                     }
621                   ]
622                                 }, 
623                           order_by      => { mfr => { value => 'asc' } },
624                           limit         => $after_limit,
625                           offset        => abs($page) * $page_size - $after_offset,
626                         }
627                 )->gather(1);
628                 push @list, map { $_->{record} } @$after;
629         }
630
631         return \@list;
632 }
633 __PACKAGE__->register_method(
634         method    => 'tag_sf_browse',
635         api_name  => 'open-ils.supercat.tag.browse',
636         api_level => 1,
637         argc      => 1,
638         signature =>
639                 { desc     => <<"                 DESC",
640 Returns a list of the requested org-scoped record ids held
641                   DESC
642                   params   =>
643                         [
644                                 { name => 'tag',
645                                   desc => 'The target MARC tag',
646                                   type => 'string' },
647                                 { name => 'subfield',
648                                   desc => 'The target MARC subfield',
649                                   type => 'string' },
650                                 { name => 'value',
651                                   desc => 'The target string',
652                                   type => 'string' },
653                                 { name => 'org_unit',
654                                   desc => 'The org unit shortname (or "-" or undef for global) to browse',
655                                   type => 'string' },
656                                 { name => 'page_size',
657                                   desc => 'Count of call numbers to retrieve, default is 9',
658                                   type => 'number' },
659                                 { name => 'page',
660                                   desc => 'The page of call numbers to retrieve, calculated based on page_size.  Can be positive, negative or 0.',
661                                   type => 'number' },
662                         ],
663                   'return' =>
664                         { desc => 'Record IDs that have copies at the relevant org units',
665                           type => 'array' }
666                 }
667 );
668
669 sub general_authority_browse {
670         my $self = shift;
671         my $client = shift;
672     return tag_sf_browse($self, $client, $self->{tag}, $self->{subfield}, @_);
673 }
674 __PACKAGE__->register_method(
675         method    => 'general_authority_browse',
676         api_name  => 'open-ils.supercat.authority.title.browse',
677         tag       => '130', subfield => 'a',
678         api_level => 1,
679         argc      => 1,
680         signature =>
681                 { desc     => "Returns a list of the requested authority record ids held",
682                   params   =>
683                         [ { name => 'value', desc => 'The target title', type => 'string' },
684                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
685                           { name => 'page', desc => 'The page of records retrieve, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
686                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
687                 }
688 );
689 __PACKAGE__->register_method(
690         method    => 'general_authority_browse',
691         api_name  => 'open-ils.supercat.authority.author.browse',
692         tag       => [qw/100 110 111/], subfield => 'a',
693         api_level => 1,
694         argc      => 1,
695         signature =>
696                 { desc     => "Returns a list of the requested authority record ids held",
697                   params   =>
698                         [ { name => 'value', desc => 'The target author', type => 'string' },
699                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
700                           { name => 'page', desc => 'The page of records retrieve, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
701                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
702                 }
703 );
704 __PACKAGE__->register_method(
705         method    => 'general_authority_browse',
706         api_name  => 'open-ils.supercat.authority.subject.browse',
707         tag       => [qw/148 150 151 155/], subfield => 'a',
708         api_level => 1,
709         argc      => 1,
710         signature =>
711                 { desc     => "Returns a list of the requested authority record ids held",
712                   params   =>
713                         [ { name => 'value', desc => 'The target subject', type => 'string' },
714                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
715                           { name => 'page', desc => 'The page of records retrieve, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
716                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
717                 }
718 );
719 __PACKAGE__->register_method(
720         method    => 'general_authority_browse',
721         api_name  => 'open-ils.supercat.authority.topic.browse',
722         tag       => '150', subfield => 'a',
723         api_level => 1,
724         argc      => 1,
725         signature =>
726                 { desc     => "Returns a list of the requested authority record ids held",
727                   params   =>
728                         [ { name => 'value', desc => 'The target topical subject', type => 'string' },
729                           { name => 'page_size', desc => 'Count of records to retrieve, default is 9', type => 'number' },
730                           { name => 'page', desc => 'The page of records retrieve, calculated based on page_size.  Can be positive, negative or 0.', type => 'number' }, ],
731                   'return' => { desc => 'Authority Record IDs that are near the target string', type => 'array' }
732                 }
733 );
734
735 sub authority_tag_sf_browse {
736         my $self = shift;
737         my $client = shift;
738
739         my $tag = shift;
740         my $subfield = shift;
741         my $value = shift;
742         my $page_size = shift || 9;
743         my $page = shift || 0;
744
745         my ($before_limit,$after_limit) = (0,0);
746         my ($before_offset,$after_offset) = (0,0);
747
748         if (!$page) {
749                 $before_limit = $after_limit = int($page_size / 2);
750                 $after_limit += 1 if ($page_size % 2);
751         } else {
752                 $before_offset = $after_offset = int($page_size / 2);
753                 $before_offset += 1 if ($page_size % 2);
754                 $before_limit = $after_limit = $page_size;
755         }
756
757         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
758
759         my @list = ();
760
761         if ($page <= 0) {
762                 my $before = $_storage->request(
763                         "open-ils.cstore.json_query.atomic",
764                         { select        => { afr => [qw/record value/] },
765                           from          => 'afr',
766                           where         => { tag => $tag, subfield => $subfield, value => { '<' => lc($value) } },
767                           order_by      => { afr => { value => 'desc' } },
768                           limit         => $before_limit,
769                           offset        => abs($page) * $page_size - $before_offset,
770                         }
771                 )->gather(1);
772                 push @list, map { $_->{record} } reverse(@$before);
773         }
774
775         if ($page >= 0) {
776                 my $after = $_storage->request(
777                         "open-ils.cstore.json_query.atomic",
778                         { select        => { afr => [qw/record value/] },
779                           from          => 'afr',
780                           where         => { tag => $tag, subfield => $subfield, value => { '>=' => lc($value) } }, 
781                           order_by      => { mfr => { value => 'asc' } },
782                           limit         => $after_limit,
783                           offset        => abs($page) * $page_size - $after_offset,
784                         }
785                 )->gather(1);
786                 push @list, map { $_->{record} } @$after;
787         }
788
789         return \@list;
790 }
791 __PACKAGE__->register_method(
792         method    => 'authority_tag_sf_browse',
793         api_name  => 'open-ils.supercat.authority.tag.browse',
794         api_level => 1,
795         argc      => 1,
796         signature =>
797                 { desc     => <<"                 DESC",
798 Returns a list of the requested authority record ids held
799                   DESC
800                   params   =>
801                         [
802                                 { name => 'tag',
803                                   desc => 'The target Authority MARC tag',
804                                   type => 'string' },
805                                 { name => 'subfield',
806                                   desc => 'The target Authority MARC subfield',
807                                   type => 'string' },
808                                 { name => 'value',
809                                   desc => 'The target string',
810                                   type => 'string' },
811                                 { name => 'page_size',
812                                   desc => 'Count of call numbers to retrieve, default is 9',
813                                   type => 'number' },
814                                 { name => 'page',
815                                   desc => 'The page of call numbers to retrieve, calculated based on page_size.  Can be positive, negative or 0.',
816                                   type => 'number' },
817                         ],
818                   'return' =>
819                         { desc => 'Authority Record IDs that are near the target string',
820                           type => 'array' }
821                 }
822 );
823
824 sub holding_data_formats {
825     return [{
826         marcxml => {
827             namespace_uri         => 'http://www.loc.gov/MARC21/slim',
828                         docs              => 'http://www.loc.gov/marcxml/',
829                         schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
830                 }
831         }];
832 }
833 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.acn.formats', api_level => 1 );
834 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.acp.formats', api_level => 1 );
835 __PACKAGE__->register_method( method => 'holding_data_formats', api_name => 'open-ils.supercat.auri.formats', api_level => 1 );
836
837
838 __PACKAGE__->register_method(
839         method    => 'retrieve_uri',
840         api_name  => 'open-ils.supercat.auri.marcxml.retrieve',
841         api_level => 1,
842         argc      => 1,
843         signature =>
844                 { desc     => <<"                 DESC",
845 Returns a fleshed call number object
846                   DESC
847                   params   =>
848                         [
849                                 { name => 'uri_id',
850                                   desc => 'An OpenILS asset::uri id',
851                                   type => 'number' },
852                         ],
853                   'return' =>
854                         { desc => 'fleshed uri',
855                           type => 'object' }
856                 }
857 );
858 sub retrieve_uri {
859         my $self = shift;
860         my $client = shift;
861         my $cpid = shift;
862         my $args = shift || {};
863
864     return OpenILS::Application::SuperCat::unAPI
865         ->new(OpenSRF::AppSession
866             ->create( 'open-ils.cstore' )
867             ->request(
868                 "open-ils.cstore.direct.asset.uri.retrieve",
869                     $cpid,
870                     { flesh             => 10,
871                           flesh_fields  => {
872                                                 auri    => [qw/call_number_maps/],
873                                                 auricnm => [qw/call_number/],
874                                                 acn         => [qw/owning_lib record/],
875                                 }
876                     })
877             ->gather(1))
878         ->as_xml($args);
879 }
880
881 __PACKAGE__->register_method(
882         method    => 'retrieve_copy',
883         api_name  => 'open-ils.supercat.acp.marcxml.retrieve',
884         api_level => 1,
885         argc      => 1,
886         signature =>
887                 { desc     => <<"                 DESC",
888 Returns a fleshed call number object
889                   DESC
890                   params   =>
891                         [
892                                 { name => 'cn_id',
893                                   desc => 'An OpenILS asset::copy id',
894                                   type => 'number' },
895                         ],
896                   'return' =>
897                         { desc => 'fleshed copy',
898                           type => 'object' }
899                 }
900 );
901 sub retrieve_copy {
902         my $self = shift;
903         my $client = shift;
904         my $cpid = shift;
905         my $args = shift || {};
906
907     return OpenILS::Application::SuperCat::unAPI
908         ->new(OpenSRF::AppSession
909             ->create( 'open-ils.cstore' )
910             ->request(
911                 "open-ils.cstore.direct.asset.copy.retrieve",
912                     $cpid,
913                     { flesh             => 2,
914                           flesh_fields  => {
915                                                 acn     => [qw/owning_lib record/],
916                                                 acp     => [qw/call_number location status circ_lib stat_cat_entries notes/],
917                                 }
918                     })
919             ->gather(1))
920         ->as_xml($args);
921 }
922
923 __PACKAGE__->register_method(
924         method    => 'retrieve_callnumber',
925         api_name  => 'open-ils.supercat.acn.marcxml.retrieve',
926         api_level => 1,
927         argc      => 1,
928         stream    => 1,
929         signature =>
930                 { desc     => <<"                 DESC",
931 Returns a fleshed call number object
932                   DESC
933                   params   =>
934                         [
935                                 { name => 'cn_id',
936                                   desc => 'An OpenILS asset::call_number id',
937                                   type => 'number' },
938                         ],
939                   'return' =>
940                         { desc => 'call number with copies',
941                           type => 'object' }
942                 }
943 );
944 sub retrieve_callnumber {
945         my $self = shift;
946         my $client = shift;
947         my $cnid = shift;
948         my $args = shift || {};
949
950     return OpenILS::Application::SuperCat::unAPI
951         ->new(OpenSRF::AppSession
952             ->create( 'open-ils.cstore' )
953             ->request(
954                 "open-ils.cstore.direct.asset.call_number.retrieve",
955                     $cnid,
956                     { flesh             => 5,
957                           flesh_fields  => {
958                                                 acn     => [qw/owning_lib record copies uri_maps/],
959                                                 auricnm => [qw/uri/],
960                                                 acp     => [qw/location status circ_lib stat_cat_entries notes/],
961                                 }
962                     })
963             ->gather(1))
964         ->as_xml($args);
965
966 }
967
968 __PACKAGE__->register_method(
969         method    => 'basic_record_holdings',
970         api_name  => 'open-ils.supercat.record.basic_holdings.retrieve',
971         api_level => 1,
972         argc      => 1,
973         stream    => 1,
974         signature =>
975                 { desc     => <<"                 DESC",
976 Returns a basic hash representation of the requested bibliographic record's holdings
977                   DESC
978                   params   =>
979                         [
980                                 { name => 'bibId',
981                                   desc => 'An OpenILS biblio::record_entry id',
982                                   type => 'number' },
983                         ],
984                   'return' =>
985                         { desc => 'Hash of bib record holdings hierarchy (call numbers and copies)',
986                           type => 'string' }
987                 }
988 );
989 sub basic_record_holdings {
990         my $self = shift;
991         my $client = shift;
992         my $bib = shift;
993         my $ou = shift;
994
995         #  holdings hold an array of call numbers, which hold an array of copies
996         #  holdings => [ label: { library, [ copies: { barcode, location, status, circ_lib } ] } ]
997         my %holdings;
998
999         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1000
1001         my $tree = $_storage->request(
1002                 "open-ils.cstore.direct.biblio.record_entry.retrieve",
1003                 $bib,
1004                 { flesh         => 5,
1005                   flesh_fields  => {
1006                                         bre     => [qw/call_numbers/],
1007                                         acn     => [qw/copies owning_lib/],
1008                                         acp     => [qw/location status circ_lib/],
1009                                 }
1010                 }
1011         )->gather(1);
1012
1013         my $o_search = { shortname => uc($ou) };
1014         if (!$ou || $ou eq '-') {
1015                 $o_search = { parent_ou => undef };
1016         }
1017
1018         my $orgs = $_storage->request(
1019                 "open-ils.cstore.direct.actor.org_unit.search",
1020                 $o_search,
1021                 { flesh         => 100,
1022                   flesh_fields  => { aou        => [qw/children/] }
1023                 }
1024         )->gather(1);
1025
1026         my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
1027
1028         $logger->debug("Searching for holdings at orgs [".join(',',@ou_ids)."], based on $ou");
1029
1030         for my $cn (@{$tree->call_numbers}) {
1031         next unless ( $cn->deleted eq 'f' || $cn->deleted == 0 );
1032
1033                 my $found = 0;
1034                 for my $c (@{$cn->copies}) {
1035                         next unless grep {$c->circ_lib->id == $_} @ou_ids;
1036                         next unless ( $c->deleted eq 'f' || $c->deleted == 0 );
1037                         $found = 1;
1038                         last;
1039                 }
1040                 next unless $found;
1041
1042                 $holdings{$cn->label}{'owning_lib'} = $cn->owning_lib->shortname;
1043
1044                 for my $cp (@{$cn->copies}) {
1045
1046                         next unless grep { $cp->circ_lib->id == $_ } @ou_ids;
1047                         next unless ( $cp->deleted eq 'f' || $cp->deleted == 0 );
1048
1049                         push @{$holdings{$cn->label}{'copies'}}, {
1050                 barcode => $cp->barcode,
1051                 status => $cp->status->name,
1052                 location => $cp->location->name,
1053                 circlib => $cp->circ_lib->shortname
1054             };
1055
1056                 }
1057         }
1058
1059         return \%holdings;
1060 }
1061
1062 #__PACKAGE__->register_method(
1063 #       method    => 'new_record_holdings',
1064 #       api_name  => 'open-ils.supercat.record.holdings_xml.retrieve',
1065 #       api_level => 1,
1066 #       argc      => 1,
1067 #       stream    => 1,
1068 #       signature =>
1069 #               { desc     => <<"                 DESC",
1070 #Returns the XML representation of the requested bibliographic record's holdings
1071 #                 DESC
1072 #                 params   =>
1073 #                       [
1074 #                               { name => 'bibId',
1075 #                                 desc => 'An OpenILS biblio::record_entry id',
1076 #                                 type => 'number' },
1077 #                       ],
1078 #                 'return' =>
1079 #                       { desc => 'Stream of bib record holdings hierarchy in XML',
1080 #                         type => 'string' }
1081 #               }
1082 #);
1083 #
1084
1085 sub new_record_holdings {
1086         my $self = shift;
1087         my $client = shift;
1088         my $bib = shift;
1089         my $ou = shift;
1090         my $flesh = shift;
1091         my $paging = shift;
1092
1093     $paging = [-1,0] if (!$paging or !ref($paging) or @$paging == 0);
1094     my $limit = $$paging[0];
1095     my $offset = $$paging[1] || 0;
1096
1097         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1098
1099         my $o_search = { shortname => uc($ou) };
1100         if (!$ou || $ou eq '-') {
1101                 $o_search = { parent_ou => undef };
1102         }
1103
1104         my $orgs = $_storage->request(
1105                 "open-ils.cstore.direct.actor.org_unit.search",
1106                 $o_search,
1107                 { flesh         => 100,
1108                   flesh_fields  => { aou        => [qw/children/] }
1109                 }
1110         )->gather(1);
1111
1112         my @ou_ids = tree_walker($orgs, 'children', sub {shift->id}) if $orgs;
1113
1114         $logger->info("Searching for holdings at orgs [".join(',',@ou_ids)."], based on $ou");
1115
1116     my %subselect = ( '-or' => [
1117         { owning_lib => \@ou_ids },
1118         { '-exists'  =>
1119             { from  => 'acp',
1120               where => {
1121                 call_number => { '=' => {'+acn'=>'id'} },
1122                 deleted => 'f',
1123                 circ_lib => \@ou_ids
1124               }
1125             }
1126         }
1127     ]);
1128
1129     if ($flesh and $flesh eq 'uris') {
1130         %subselect = (
1131             owning_lib => \@ou_ids,
1132             '-exists'  => {
1133                 from  => { auricnm => 'auri' },
1134                 where => {
1135                     call_number => { '=' => {'+acn'=>'id'} },
1136                     '+auri' => { active => 't' }
1137                 }
1138             }
1139         );
1140     }
1141
1142
1143         my $cns = $_storage->request(
1144                 "open-ils.cstore.direct.asset.call_number.search.atomic",
1145                 { record  => $bib,
1146           deleted => 'f',
1147           %subselect
1148         },
1149                 { flesh         => 5,
1150                   flesh_fields  => {
1151                                         acn     => [qw/copies owning_lib uri_maps/],
1152                                         auricnm => [qw/uri/],
1153                                         acp     => [qw/circ_lib location status stat_cat_entries notes/],
1154                                         asce    => [qw/stat_cat/],
1155                                 },
1156           ( $limit > -1 ? ( limit  => $limit  ) : () ),
1157           ( $offset     ? ( offset => $offset ) : () ),
1158           order_by  => { acn => { label => {} } }
1159                 }
1160         )->gather(1);
1161
1162         my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
1163         $year += 1900;
1164         $month += 1;
1165
1166         $client->respond("<volumes xmlns='http://open-ils.org/spec/holdings/v1'>\n");
1167     
1168         for my $cn (@$cns) {
1169                 next unless (@{$cn->copies} > 0 or (ref($cn->uri_maps) and @{$cn->uri_maps}));
1170
1171                 # We don't want O:A:S:unAPI::acn to return the record, we've got that already
1172                 # In the context of BibTemplate, copies aren't necessary because we pull those
1173                 # in a separate call
1174         $client->respond(
1175             OpenILS::Application::SuperCat::unAPI::acn
1176                 ->new( $cn )
1177                 ->as_xml( {no_record => 1, no_copies => ($flesh ? 0 : 1)} )
1178         );
1179         }
1180
1181         return "</volumes>\n";
1182 }
1183 __PACKAGE__->register_method(
1184         method    => 'new_record_holdings',
1185         api_name  => 'open-ils.supercat.record.holdings_xml.retrieve',
1186         api_level => 1,
1187         argc      => 1,
1188         stream    => 1,
1189         signature =>
1190                 { desc     => <<"                 DESC",
1191 Returns the XML representation of the requested bibliographic record's holdings
1192                   DESC
1193                   params   =>
1194                         [
1195                                 { name => 'bibId',
1196                                   desc => 'An OpenILS biblio::record_entry ID',
1197                                   type => 'number' },
1198                                 { name => 'orgUnit',
1199                                   desc => 'An OpenILS actor::org_unit short name that limits the scope of returned holdings',
1200                                   type => 'text' },
1201                                 { name => 'hideCopies',
1202                                   desc => 'Flag that prevents the inclusion of copies in the returned holdings',
1203                                   type => 'boolean' },
1204                                 { name => 'paging',
1205                                   desc => 'Arry of limit and offset for holdings paging',
1206                                   type => 'array' },
1207                         ],
1208                   'return' =>
1209                         { desc => 'Stream of bib record holdings hierarchy in XML',
1210                           type => 'string' }
1211                 }
1212 );
1213
1214 sub isbn_holdings {
1215         my $self = shift;
1216         my $client = shift;
1217         my $isbn = shift;
1218
1219         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1220
1221         my $recs = $_storage->request(
1222                         'open-ils.cstore.direct.metabib.full_rec.search.atomic',
1223                         { tag => { like => '02%'}, value => {like => "$isbn\%"}}
1224         )->gather(1);
1225
1226         return undef unless (@$recs);
1227
1228         return ($self->method_lookup( 'open-ils.supercat.record.holdings_xml.retrieve')->run( $recs->[0]->record ))[0];
1229 }
1230 __PACKAGE__->register_method(
1231         method    => 'isbn_holdings',
1232         api_name  => 'open-ils.supercat.isbn.holdings_xml.retrieve',
1233         api_level => 1,
1234         argc      => 1,
1235         signature =>
1236                 { desc     => <<"                 DESC",
1237 Returns the XML representation of the requested bibliographic record's holdings
1238                   DESC
1239                   params   =>
1240                         [
1241                                 { name => 'isbn',
1242                                   desc => 'An isbn',
1243                                   type => 'string' },
1244                         ],
1245                   'return' =>
1246                         { desc => 'The bib record holdings hierarchy in XML',
1247                           type => 'string' }
1248                 }
1249 );
1250
1251 sub escape {
1252         my $self = shift;
1253         my $text = shift;
1254     return '' unless $text;
1255         $text =~ s/&/&amp;/gsom;
1256         $text =~ s/</&lt;/gsom;
1257         $text =~ s/>/&gt;/gsom;
1258         $text =~ s/"/\\"/gsom;
1259         return $text;
1260 }
1261
1262 sub recent_changes {
1263         my $self = shift;
1264         my $client = shift;
1265         my $when = shift || '1-01-01';
1266         my $limit = shift;
1267
1268         my $type = 'biblio';
1269         $type = 'authority' if ($self->api_name =~ /authority/o);
1270
1271         my $axis = 'create_date';
1272         $axis = 'edit_date' if ($self->api_name =~ /edit/o);
1273
1274         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1275
1276         return $_storage->request(
1277                 "open-ils.cstore.direct.$type.record_entry.id_list.atomic",
1278                 { $axis => { ">" => $when }, id => { '>' => 0 } },
1279                 { order_by => { bre => "$axis desc" }, limit => $limit }
1280         )->gather(1);
1281 }
1282
1283 for my $t ( qw/biblio authority/ ) {
1284         for my $a ( qw/import edit/ ) {
1285
1286                 __PACKAGE__->register_method(
1287                         method    => 'recent_changes',
1288                         api_name  => "open-ils.supercat.$t.record.$a.recent",
1289                         api_level => 1,
1290                         argc      => 0,
1291                         signature =>
1292                                 { desc     => "Returns a list of recently ${a}ed $t records",
1293                                   params   =>
1294                                         [
1295                                                 { name => 'when',
1296                                                   desc => "Date to start looking for ${a}ed records",
1297                                                   default => '1-01-01',
1298                                                   type => 'string' },
1299
1300                                                 { name => 'limit',
1301                                                   desc => "Maximum count to retrieve",
1302                                                   type => 'number' },
1303                                         ],
1304                                   'return' =>
1305                                         { desc => "An id list of $t records",
1306                                           type => 'array' }
1307                                 },
1308                 );
1309         }
1310 }
1311
1312
1313 sub retrieve_record_marcxml {
1314         my $self = shift;
1315         my $client = shift;
1316         my $rid = shift;
1317
1318         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1319
1320         my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $rid )->gather(1);
1321         return $U->entityize( $record->marc ) if ($record);
1322         return undef;
1323 }
1324
1325 __PACKAGE__->register_method(
1326         method    => 'retrieve_record_marcxml',
1327         api_name  => 'open-ils.supercat.record.marcxml.retrieve',
1328         api_level => 1,
1329         argc      => 1,
1330         signature =>
1331                 { desc     => <<"                 DESC",
1332 Returns the MARCXML representation of the requested bibliographic record
1333                   DESC
1334                   params   =>
1335                         [
1336                                 { name => 'bibId',
1337                                   desc => 'An OpenILS biblio::record_entry id',
1338                                   type => 'number' },
1339                         ],
1340                   'return' =>
1341                         { desc => 'The bib record in MARCXML',
1342                           type => 'string' }
1343                 }
1344 );
1345
1346 sub retrieve_isbn_marcxml {
1347         my $self = shift;
1348         my $client = shift;
1349         my $isbn = shift;
1350
1351         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1352
1353         my $recs = $_storage->request(
1354                         'open-ils.cstore.direct.metabib.full_rec.search.atomic',
1355                         { tag => { like => '02%'}, value => {like => "$isbn\%"}}
1356         )->gather(1);
1357
1358         return undef unless (@$recs);
1359
1360         my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
1361         return $U->entityize( $record->marc ) if ($record);
1362         return undef;
1363 }
1364
1365 __PACKAGE__->register_method(
1366         method    => 'retrieve_isbn_marcxml',
1367         api_name  => 'open-ils.supercat.isbn.marcxml.retrieve',
1368         api_level => 1,
1369         argc      => 1,
1370         signature =>
1371                 { desc     => <<"                 DESC",
1372 Returns the MARCXML representation of the requested ISBN
1373                   DESC
1374                   params   =>
1375                         [
1376                                 { name => 'ISBN',
1377                                   desc => 'An ... um ... ISBN',
1378                                   type => 'string' },
1379                         ],
1380                   'return' =>
1381                         { desc => 'The bib record in MARCXML',
1382                           type => 'string' }
1383                 }
1384 );
1385
1386 sub retrieve_record_transform {
1387         my $self = shift;
1388         my $client = shift;
1389         my $rid = shift;
1390
1391         (my $transform = $self->api_name) =~ s/^.+record\.([^\.]+)\.retrieve$/$1/o;
1392
1393         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1394         #$_storage->connect;
1395
1396         my $record = $_storage->request(
1397                 'open-ils.cstore.direct.biblio.record_entry.retrieve',
1398                 $rid
1399         )->gather(1);
1400
1401         return undef unless ($record);
1402
1403         return $U->entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $record->marc ) )->toString);
1404 }
1405
1406 sub retrieve_isbn_transform {
1407         my $self = shift;
1408         my $client = shift;
1409         my $isbn = shift;
1410
1411         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1412
1413         my $recs = $_storage->request(
1414                         'open-ils.cstore.direct.metabib.full_rec.search.atomic',
1415                         { tag => { like => '02%'}, value => {like => "$isbn\%"}}
1416         )->gather(1);
1417
1418         return undef unless (@$recs);
1419
1420         (my $transform = $self->api_name) =~ s/^.+isbn\.([^\.]+)\.retrieve$/$1/o;
1421
1422         my $record = $_storage->request( 'open-ils.cstore.direct.biblio.record_entry.retrieve' => $recs->[0]->record )->gather(1);
1423
1424         return undef unless ($record);
1425
1426         return $U->entityize($record_xslt{$transform}{xslt}->transform( $_parser->parse_string( $record->marc ) )->toString);
1427 }
1428
1429 sub retrieve_record_objects {
1430         my $self = shift;
1431         my $client = shift;
1432         my $ids = shift;
1433
1434         $ids = [$ids] unless (ref $ids);
1435         $ids = [grep {$_} @$ids];
1436
1437         return [] unless (@$ids);
1438
1439         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1440         return $_storage->request('open-ils.cstore.direct.biblio.record_entry.search.atomic' => { id => [grep {$_} @$ids] })->gather(1);
1441 }
1442 __PACKAGE__->register_method(
1443         method    => 'retrieve_record_objects',
1444         api_name  => 'open-ils.supercat.record.object.retrieve',
1445         api_level => 1,
1446         argc      => 1,
1447         signature =>
1448                 { desc     => <<"                 DESC",
1449 Returns the Fieldmapper object representation of the requested bibliographic records
1450                   DESC
1451                   params   =>
1452                         [
1453                                 { name => 'bibIds',
1454                                   desc => 'OpenILS biblio::record_entry ids',
1455                                   type => 'array' },
1456                         ],
1457                   'return' =>
1458                         { desc => 'The bib records',
1459                           type => 'array' }
1460                 }
1461 );
1462
1463
1464 sub retrieve_isbn_object {
1465         my $self = shift;
1466         my $client = shift;
1467         my $isbn = shift;
1468
1469         return undef unless ($isbn);
1470
1471         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1472         my $recs = $_storage->request(
1473                         'open-ils.cstore.direct.metabib.full_rec.search.atomic',
1474                         { tag => { like => '02%'}, value => {like => "$isbn\%"}}
1475         )->gather(1);
1476
1477         return undef unless (@$recs);
1478
1479         return $_storage->request(
1480                 'open-ils.cstore.direct.biblio.record_entry.search.atomic',
1481                 { id => $recs->[0]->record }
1482         )->gather(1);
1483 }
1484 __PACKAGE__->register_method(
1485         method    => 'retrieve_isbn_object',
1486         api_name  => 'open-ils.supercat.isbn.object.retrieve',
1487         api_level => 1,
1488         argc      => 1,
1489         signature =>
1490                 { desc     => <<"                 DESC",
1491 Returns the Fieldmapper object representation of the requested bibliographic record
1492                   DESC
1493                   params   =>
1494                         [
1495                                 { name => 'isbn',
1496                                   desc => 'an ISBN',
1497                                   type => 'string' },
1498                         ],
1499                   'return' =>
1500                         { desc => 'The bib record',
1501                           type => 'object' }
1502                 }
1503 );
1504
1505
1506
1507 sub retrieve_metarecord_mods {
1508         my $self = shift;
1509         my $client = shift;
1510         my $rid = shift;
1511
1512         my $_storage = OpenSRF::AppSession->connect( 'open-ils.cstore' );
1513
1514         # Get the metarecord in question
1515         my $mr =
1516         $_storage->request(
1517                 'open-ils.cstore.direct.metabib.metarecord.retrieve' => $rid
1518         )->gather(1);
1519
1520         # Now get the map of all bib records for the metarecord
1521         my $recs =
1522         $_storage->request(
1523                 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
1524                 {metarecord => $rid}
1525         )->gather(1);
1526
1527         $logger->debug("Adding ".scalar(@$recs)." bib record to the MODS of the metarecord");
1528
1529         # and retrieve the lead (master) record as MODS
1530         my ($master) =
1531                 $self   ->method_lookup('open-ils.supercat.record.mods.retrieve')
1532                         ->run($mr->master_record);
1533         my $master_mods = $_parser->parse_string($master)->documentElement;
1534         $master_mods->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
1535
1536         # ... and a MODS clone to populate, with guts removed.
1537         my $mods = $_parser->parse_string($master)->documentElement;
1538         $mods->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
1539         ($mods) = $mods->findnodes('//mods:mods');
1540         $mods->removeChildNodes;
1541
1542         # Add the metarecord ID as a (locally defined) info URI
1543         my $recordInfo = $mods
1544                 ->ownerDocument
1545                 ->createElement("mods:recordInfo");
1546
1547         my $recordIdentifier = $mods
1548                 ->ownerDocument
1549                 ->createElement("mods:recordIdentifier");
1550
1551         my ($year,$month,$day) = reverse( (localtime)[3,4,5] );
1552         $year += 1900;
1553         $month += 1;
1554
1555         my $id = $mr->id;
1556         $recordIdentifier->appendTextNode(
1557                 sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:metabib-metarecord/$id", $month, $day)
1558         );
1559
1560         $recordInfo->appendChild($recordIdentifier);
1561         $mods->appendChild($recordInfo);
1562
1563         # Grab the title, author and ISBN for the master record and populate the metarecord
1564         my ($title) = $master_mods->findnodes( './mods:titleInfo[not(@type)]' );
1565         
1566         if ($title) {
1567                 $title->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
1568                 $title = $mods->ownerDocument->importNode($title);
1569                 $mods->appendChild($title);
1570         }
1571
1572         my ($author) = $master_mods->findnodes( './mods:name[mods:role/mods:text[text()="creator"]]' );
1573         if ($author) {
1574                 $author->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
1575                 $author = $mods->ownerDocument->importNode($author);
1576                 $mods->appendChild($author);
1577         }
1578
1579         my ($isbn) = $master_mods->findnodes( './mods:identifier[@type="isbn"]' );
1580         if ($isbn) {
1581                 $isbn->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
1582                 $isbn = $mods->ownerDocument->importNode($isbn);
1583                 $mods->appendChild($isbn);
1584         }
1585
1586         # ... and loop over the constituent records
1587         for my $map ( @$recs ) {
1588
1589                 # get the MODS
1590                 my ($rec) =
1591                         $self   ->method_lookup('open-ils.supercat.record.mods.retrieve')
1592                                 ->run($map->source);
1593
1594                 my $part_mods = $_parser->parse_string($rec);
1595                 $part_mods->documentElement->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
1596                 ($part_mods) = $part_mods->findnodes('//mods:mods');
1597
1598                 for my $node ( ($part_mods->findnodes( './mods:subject' )) ) {
1599                         $node->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
1600                         $node = $mods->ownerDocument->importNode($node);
1601                         $mods->appendChild( $node );
1602                 }
1603
1604                 my $relatedItem = $mods
1605                         ->ownerDocument
1606                         ->createElement("mods:relatedItem");
1607
1608                 $relatedItem->setAttribute( type => 'constituent' );
1609
1610                 my $identifier = $mods
1611                         ->ownerDocument
1612                         ->createElement("mods:identifier");
1613
1614                 $identifier->setAttribute( type => 'uri' );
1615
1616                 my $subRecordInfo = $mods
1617                         ->ownerDocument
1618                         ->createElement("mods:recordInfo");
1619
1620                 my $subRecordIdentifier = $mods
1621                         ->ownerDocument
1622                         ->createElement("mods:recordIdentifier");
1623
1624                 my $subid = $map->source;
1625                 $subRecordIdentifier->appendTextNode(
1626                         sprintf("tag:open-ils.org,$year-\%0.2d-\%0.2d:biblio-record_entry/$subid",
1627                                 $month,
1628                                 $day
1629                         )
1630                 );
1631                 $subRecordInfo->appendChild($subRecordIdentifier);
1632
1633                 $relatedItem->appendChild( $subRecordInfo );
1634
1635                 my ($tor) = $part_mods->findnodes( './mods:typeOfResource' );
1636                 $tor->setNamespace( "http://www.loc.gov/mods/", "mods", 1 ) if ($tor);
1637                 $tor = $mods->ownerDocument->importNode($tor) if ($tor);
1638                 $relatedItem->appendChild($tor) if ($tor);
1639
1640                 if ( my ($part_isbn) = $part_mods->findnodes( './mods:identifier[@type="isbn"]' ) ) {
1641                         $part_isbn->setNamespace( "http://www.loc.gov/mods/", "mods", 1 );
1642                         $part_isbn = $mods->ownerDocument->importNode($part_isbn);
1643                         $relatedItem->appendChild( $part_isbn );
1644
1645                         if (!$isbn) {
1646                                 $isbn = $mods->appendChild( $part_isbn->cloneNode(1) );
1647                         }
1648                 }
1649
1650                 $mods->appendChild( $relatedItem );
1651
1652         }
1653
1654         $_storage->disconnect;
1655
1656         return $U->entityize($mods->toString);
1657
1658 }
1659 __PACKAGE__->register_method(
1660         method    => 'retrieve_metarecord_mods',
1661         api_name  => 'open-ils.supercat.metarecord.mods.retrieve',
1662         api_level => 1,
1663         argc      => 1,
1664         signature =>
1665                 { desc     => <<"                 DESC",
1666 Returns the MODS representation of the requested metarecord
1667                   DESC
1668                   params   =>
1669                         [
1670                                 { name => 'metarecordId',
1671                                   desc => 'An OpenILS metabib::metarecord id',
1672                                   type => 'number' },
1673                         ],
1674                   'return' =>
1675                         { desc => 'The metarecord in MODS',
1676                           type => 'string' }
1677                 }
1678 );
1679
1680 sub list_metarecord_formats {
1681         my @list = (
1682                 { mods =>
1683                         { namespace_uri   => 'http://www.loc.gov/mods/',
1684                           docs            => 'http://www.loc.gov/mods/',
1685                           schema_location => 'http://www.loc.gov/standards/mods/mods.xsd',
1686                         }
1687                 }
1688         );
1689
1690         for my $type ( keys %metarecord_xslt ) {
1691                 push @list,
1692                         { $type => 
1693                                 { namespace_uri   => $metarecord_xslt{$type}{namespace_uri},
1694                                   docs            => $metarecord_xslt{$type}{docs},
1695                                   schema_location => $metarecord_xslt{$type}{schema_location},
1696                                 }
1697                         };
1698         }
1699
1700         return \@list;
1701 }
1702 __PACKAGE__->register_method(
1703         method    => 'list_metarecord_formats',
1704         api_name  => 'open-ils.supercat.metarecord.formats',
1705         api_level => 1,
1706         argc      => 0,
1707         signature =>
1708                 { desc     => <<"                 DESC",
1709 Returns the list of valid metarecord formats that supercat understands.
1710                   DESC
1711                   'return' =>
1712                         { desc => 'The format list',
1713                           type => 'array' }
1714                 }
1715 );
1716
1717
1718 sub list_record_formats {
1719         my @list = (
1720                 { marcxml =>
1721                         { namespace_uri   => 'http://www.loc.gov/MARC21/slim',
1722                           docs            => 'http://www.loc.gov/marcxml/',
1723                           schema_location => 'http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd',
1724                         }
1725                 }
1726         );
1727
1728         for my $type ( keys %record_xslt ) {
1729                 push @list,
1730                         { $type => 
1731                                 { namespace_uri   => $record_xslt{$type}{namespace_uri},
1732                                   docs            => $record_xslt{$type}{docs},
1733                                   schema_location => $record_xslt{$type}{schema_location},
1734                                 }
1735                         };
1736         }
1737
1738         return \@list;
1739 }
1740 __PACKAGE__->register_method(
1741         method    => 'list_record_formats',
1742         api_name  => 'open-ils.supercat.record.formats',
1743         api_level => 1,
1744         argc      => 0,
1745         signature =>
1746                 { desc     => <<"                 DESC",
1747 Returns the list of valid record formats that supercat understands.
1748                   DESC
1749                   'return' =>
1750                         { desc => 'The format list',
1751                           type => 'array' }
1752                 }
1753 );
1754 __PACKAGE__->register_method(
1755         method    => 'list_record_formats',
1756         api_name  => 'open-ils.supercat.isbn.formats',
1757         api_level => 1,
1758         argc      => 0,
1759         signature =>
1760                 { desc     => <<"                 DESC",
1761 Returns the list of valid record formats that supercat understands.
1762                   DESC
1763                   'return' =>
1764                         { desc => 'The format list',
1765                           type => 'array' }
1766                 }
1767 );
1768
1769
1770 sub oISBN {
1771         my $self = shift;
1772         my $client = shift;
1773         my $isbn = shift;
1774
1775         $isbn =~ s/-//gso;
1776
1777         throw OpenSRF::EX::InvalidArg ('I need an ISBN please')
1778                 unless (length($isbn) >= 10);
1779
1780         my $_storage = OpenSRF::AppSession->create( 'open-ils.cstore' );
1781
1782         # Create a storage session, since we'll be making muliple requests.
1783         $_storage->connect;
1784
1785         # Find the record that has that ISBN.
1786         my $bibrec = $_storage->request(
1787                 'open-ils.cstore.direct.metabib.full_rec.search.atomic',
1788                 { tag => '020', subfield => 'a', value => { like => lc($isbn).'%'} }
1789         )->gather(1);
1790
1791         # Go away if we don't have one.
1792         return {} unless (@$bibrec);
1793
1794         # Find the metarecord for that bib record.
1795         my $mr = $_storage->request(
1796                 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
1797                 {source => $bibrec->[0]->record}
1798         )->gather(1);
1799
1800         # Find the other records for that metarecord.
1801         my $records = $_storage->request(
1802                 'open-ils.cstore.direct.metabib.metarecord_source_map.search.atomic',
1803                 {metarecord => $mr->[0]->metarecord}
1804         )->gather(1);
1805
1806         # Just to be safe.  There's currently no unique constraint on sources...
1807         my %unique_recs = map { ($_->source, 1) } @$records;
1808         my @rec_list = sort keys %unique_recs;
1809
1810         # And now fetch the ISBNs for thos records.
1811         my $recs = [];
1812         push @$recs,
1813                 $_storage->request(
1814                         'open-ils.cstore.direct.metabib.full_rec.search',
1815                         { tag => '020', subfield => 'a', record => $_ }
1816                 )->gather(1) for (@rec_list);
1817
1818         # We're done with the storage server session.
1819         $_storage->disconnect;
1820
1821         # Return the oISBN data structure.  This will be XMLized at a higher layer.
1822         return
1823                 { metarecord => $mr->[0]->metarecord,
1824                   record_list => { map { $_ ? ($_->record, $_->value) : () } @$recs } };
1825
1826 }
1827 __PACKAGE__->register_method(
1828         method    => 'oISBN',
1829         api_name  => 'open-ils.supercat.oisbn',
1830         api_level => 1,
1831         argc      => 1,
1832         signature =>
1833                 { desc     => <<"                 DESC",
1834 Returns the ISBN list for the metarecord of the requested isbn
1835                   DESC
1836                   params   =>
1837                         [
1838                                 { name => 'isbn',
1839                                   desc => 'An ISBN.  Duh.',
1840                                   type => 'string' },
1841                         ],
1842                   'return' =>
1843                         { desc => 'record to isbn map',
1844                           type => 'object' }
1845                 }
1846 );
1847
1848 package OpenILS::Application::SuperCat::unAPI;
1849 use base qw/OpenILS::Application::SuperCat/;
1850
1851 sub as_xml {
1852     die "dummy superclass, use a real class";
1853 }
1854
1855 sub new {
1856     my $class = shift;
1857     my $obj = shift;
1858     return unless ($obj);
1859
1860     $class = ref($class) || $class;
1861
1862     if ($class eq __PACKAGE__) {
1863         return unless (ref($obj));
1864         $class .= '::' . $obj->json_hint;
1865     }
1866
1867     return bless { obj => $obj } => $class;
1868 }
1869
1870 sub obj {
1871     my $self = shift;
1872     return $self->{obj};
1873 }
1874
1875 package OpenILS::Application::SuperCat::unAPI::auri;
1876 use base qw/OpenILS::Application::SuperCat::unAPI/;
1877
1878 sub as_xml {
1879     my $self = shift;
1880     my $args = shift;
1881
1882     my $xml = '      <uri xmlns="http://open-ils.org/spec/holdings/v1" ';
1883     $xml .= 'id="tag:open-ils.org:asset-uri/' . $self->obj->id . '" ';
1884     $xml .= 'use_restriction="' . $self->escape( $self->obj->use_restriction ) . '" ';
1885     $xml .= 'label="' . $self->escape( $self->obj->label ) . '" ';
1886     $xml .= 'href="' . $self->escape( $self->obj->href ) . '">';
1887
1888     if (!$args->{no_volumes}) {
1889         if (ref($self->obj->call_number_maps) && @{ $self->obj->call_number_maps }) {
1890             $xml .= "      <volumes>\n" . join(
1891                 '',
1892                 map {
1893                     OpenILS::Application::SuperCat::unAPI
1894                         ->new( $_->call_number )
1895                         ->as_xml({ %$args, no_uris=>1, no_copies=>1 })
1896                 } @{ $self->obj->call_number_maps }
1897             ) . "      </volumes>\n";
1898
1899         } else {
1900             $xml .= "      <volumes/>\n";
1901         }
1902     }
1903
1904     $xml .= "      </uri>\n";
1905
1906     return $xml;
1907 }
1908
1909 package OpenILS::Application::SuperCat::unAPI::acn;
1910 use base qw/OpenILS::Application::SuperCat::unAPI/;
1911
1912 sub as_xml {
1913     my $self = shift;
1914     my $args = shift;
1915
1916     my $xml = '    <volume xmlns="http://open-ils.org/spec/holdings/v1" ';
1917
1918     $xml .= 'id="tag:open-ils.org:asset-call_number/' . $self->obj->id . '" ';
1919     $xml .= 'lib="' . $self->escape( $self->obj->owning_lib->shortname ) . '" ';
1920     $xml .= 'label="' . $self->escape( $self->obj->label ) . '">';
1921     $xml .= "\n";
1922
1923     if (!$args->{no_copies}) {
1924         if (ref($self->obj->copies) && @{ $self->obj->copies }) {
1925             $xml .= "      <copies>\n" . join(
1926                 '',
1927                 map {
1928                     OpenILS::Application::SuperCat::unAPI
1929                         ->new( $_ )
1930                         ->as_xml({ %$args, no_volume=>1 })
1931                 } @{ $self->obj->copies }
1932             ) . "      </copies>\n";
1933
1934         } else {
1935             $xml .= "      <copies/>\n";
1936         }
1937     }
1938
1939     if (!$args->{no_uris}) {
1940         if (ref($self->obj->uri_maps) && @{ $self->obj->uri_maps }) {
1941             $xml .= "      <uris>\n" . join(
1942                 '',
1943                 map {
1944                     OpenILS::Application::SuperCat::unAPI
1945                         ->new( $_->uri )
1946                         ->as_xml({ %$args, no_volumes=>1 })
1947                 } @{ $self->obj->uri_maps }
1948             ) . "      </uris>\n";
1949
1950         } else {
1951             $xml .= "      <uris/>\n";
1952         }
1953     }
1954
1955
1956     $xml .= '      <owning_lib xmlns="http://open-ils.org/spec/actors/v1" ';
1957     $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->owning_lib->id . '" ';
1958     $xml .= 'shortname="'.$self->escape( $self->obj->owning_lib->shortname ) .'" ';
1959     $xml .= 'name="'.$self->escape( $self->obj->owning_lib->name ) .'"/>';
1960     $xml .= "\n";
1961
1962     unless ($args->{no_record}) {
1963         my $rec_tag = "tag:open-ils.org:biblio-record_entry/".$self->obj->record->id.'/'.$self->escape( $self->obj->owning_lib->shortname ) ;
1964
1965         my $r_doc = $parser->parse_string($self->obj->record->marc);
1966         $r_doc->documentElement->setAttribute( id => $rec_tag );
1967         $xml .= $U->entityize($r_doc->documentElement->toString);
1968     }
1969
1970     $xml .= "    </volume>\n";
1971
1972     return $xml;
1973 }
1974
1975 package OpenILS::Application::SuperCat::unAPI::acp;
1976 use base qw/OpenILS::Application::SuperCat::unAPI/;
1977
1978 sub as_xml {
1979     my $self = shift;
1980     my $args = shift;
1981
1982     my $xml = '      <copy xmlns="http://open-ils.org/spec/holdings/v1" '.
1983         'id="tag:open-ils.org:asset-copy/' . $self->obj->id . '" ';
1984
1985     $xml .= $_ . '="' . $self->escape( $self->obj->$_  ) . '" ' for (qw/
1986         create_date edit_date copy_number circulate deposit ref holdable deleted
1987         deposit_amount price barcode circ_modifier circ_as_type opac_visible
1988     /);
1989
1990     $xml .= ">\n";
1991
1992     $xml .= '        <status ident="' . $self->obj->status->id . '">' . $self->escape( $self->obj->status->name  ) . "</status>\n";
1993     $xml .= '        <location ident="' . $self->obj->location->id . '">' . $self->escape( $self->obj->location->name  ) . "</location>\n";
1994     $xml .= '        <circlib ident="' . $self->obj->circ_lib->id . '">' . $self->escape( $self->obj->circ_lib->name  ) . "</circlib>\n";
1995
1996     $xml .= '        <circ_lib xmlns="http://open-ils.org/spec/actors/v1" ';
1997     $xml .= 'id="tag:open-ils.org:actor-org_unit/' . $self->obj->circ_lib->id . '" ';
1998     $xml .= 'shortname="'.$self->escape( $self->obj->circ_lib->shortname ) .'" ';
1999     $xml .= 'name="'.$self->escape( $self->obj->circ_lib->name ) .'"/>';
2000     $xml .= "\n";
2001
2002         $xml .= "        <copy_notes>\n";
2003         if (ref($self->obj->notes) && $self->obj->notes) {
2004                 for my $note ( @{$self->obj->notes} ) {
2005                         next unless ( $note->pub eq 't' );
2006                         $xml .= sprintf('        <copy_note date="%s" title="%s">%s</copy_note>',$note->create_date, $self->escape($note->title), $self->escape($note->value));
2007                         $xml .= "\n";
2008                 }
2009         }
2010
2011         $xml .= "        </copy_notes>\n";
2012     $xml .= "        <statcats>\n";
2013
2014         if (ref($self->obj->stat_cat_entries) && $self->obj->stat_cat_entries) {
2015                 for my $sce ( @{$self->obj->stat_cat_entries} ) {
2016                         next unless ( $sce->stat_cat->opac_visible eq 't' );
2017                         $xml .= sprintf('          <statcat name="%s">%s</statcat>',$self->escape($sce->stat_cat->name) ,$self->escape($sce->value));
2018                         $xml .= "\n";
2019                 }
2020         }
2021         $xml .= "        </statcats>\n";
2022
2023     unless ($args->{no_volume}) {
2024         if (ref($self->obj->call_number)) {
2025             $xml .= OpenILS::Application::SuperCat::unAPI
2026                         ->new( $self->obj->call_number )
2027                         ->as_xml({ %$args, no_copies=>1 });
2028         } else {
2029             $xml .= "    <volume/>\n";
2030         }
2031     }
2032
2033     $xml .= "      </copy>\n";
2034
2035     return $xml;
2036 }
2037
2038
2039 1;
2040 # vim: noet:ts=4:sw=4