]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Cat/BibCommon.pm
Added global_flag and support for using the internal record
[working/Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Cat / BibCommon.pm
1 package OpenILS::Application::Cat::BibCommon;
2 use strict; use warnings;
3 use OpenILS::Utils::CStoreEditor q/:funcs/;
4 use OpenSRF::Utils::Logger qw($logger);
5 use OpenILS::Application::AppUtils;
6 use OpenILS::Utils::Fieldmapper;
7 use OpenILS::Const qw/:const/;
8 use OpenSRF::AppSession;
9 use OpenILS::Event;
10 my $U = 'OpenILS::Application::AppUtils';
11 my $MARC_NAMESPACE = 'http://www.loc.gov/MARC21/slim';
12
13
14 # ---------------------------------------------------------------------------
15 # Shared bib mangling code.  Do not publish methods from here.
16 # ---------------------------------------------------------------------------
17
18 my $__bib_sources;
19 sub bib_source_from_name {
20         my $name = shift;
21         $logger->debug("searching for bib source: $name");
22
23         fetch_bib_sources();
24
25         my ($s) = grep { lc($_->source) eq lc($name) } @$__bib_sources;
26
27         return $s->id if $s;
28         return undef;
29 }
30
31 sub fetch_bib_sources {
32         $__bib_sources = new_editor()->retrieve_all_config_bib_source()
33                 unless $__bib_sources;
34         return $__bib_sources;
35 }
36
37
38 sub biblio_record_replace_marc  {
39         my($class, $e, $recid, $newxml, $source, $fixtcn, $override) = @_;
40
41         my $rec = $e->retrieve_biblio_record_entry($recid)
42                 or return $e->die_event;
43
44     # See if there is a different record in the database that has our TCN value
45     # If we're not updating the TCN, all we care about it the marcdoc
46     # XXX should .update even bother with the tcn_info if it's not going to replace it?
47     # there is the potential for returning a TCN_EXISTS event, even though no replacement happens
48
49         my( $tcn, $tsource, $marcdoc, $evt);
50
51     if($fixtcn or $override) {
52
53             ($tcn, $tsource, $marcdoc, $evt) = 
54                     _find_tcn_info($e, $newxml, $override, $recid);
55
56             return $evt if $evt;
57
58                 $rec->tcn_value($tcn) if ($tcn);
59                 $rec->tcn_source($tsource);
60
61     } else {
62
63         $marcdoc = __make_marc_doc($newxml);
64     }
65
66
67         $rec->source(bib_source_from_name($source)) if $source;
68         $rec->editor($e->requestor->id);
69         $rec->edit_date('now');
70         $rec->marc( $U->entityize( $marcdoc->documentElement->toString ) );
71         $e->update_biblio_record_entry($rec) or return $e->die_event;
72
73         return $rec;
74 }
75
76 sub biblio_record_xml_import {
77         my($class, $e, $xml, $source, $auto_tcn, $override) = @_;
78
79         my( $evt, $tcn, $tcn_source, $marcdoc );
80
81     my $use_id = $e->retrieve_config_global_flag('cat.bib.use_id_for_tcn');
82     $use_id = ($use_id and $U->is_true($use_id->enabled));
83
84         if( $auto_tcn or $use_id ) {
85                 # auto_tcn forces a blank TCN value so the DB will have to generate one for us
86                 $marcdoc = __make_marc_doc($xml);
87         } else {
88                 ( $tcn, $tcn_source, $marcdoc, $evt ) = _find_tcn_info($e, $xml, $override);
89                 return $evt if $evt;
90         }
91
92         $logger->info("user ".$e->requestor->id.
93                 " creating new biblio entry with tcn=$tcn and tcn_source $tcn_source");
94
95         my $record = Fieldmapper::biblio::record_entry->new;
96
97         $record->source(bib_source_from_name($source)) if $source;
98         $record->tcn_source($tcn_source);
99         $record->tcn_value($tcn) if ($tcn);
100         $record->creator($e->requestor->id);
101         $record->editor($e->requestor->id);
102         $record->create_date('now');
103         $record->edit_date('now');
104         $record->marc($U->entityize($marcdoc->documentElement->toString));
105
106     $record = $e->create_biblio_record_entry($record) or return $e->die_event;
107
108     if($use_id) {
109         my $existing = $e->search_biblio_record_entry(
110             {   
111                 tcn_value => $record->id,
112                 deleted => 'f'
113             }, { 
114                 idlist => 1 
115             }
116         );
117
118         if(@$existing) {
119             # leave the auto-generated tcn_value in place
120             $logger->warn("Collision using internal ID as tcn_value for record " . $record->id);
121         } else {
122             $record->tcn_value($record->id);
123             $e->update_biblio_record_entry($record) or return $e->die_event;
124         }
125     }
126
127         $logger->info("marc create/import created new record ".$record->id);
128         return $record;
129 }
130
131 sub __make_marc_doc {
132         my $xml = shift;
133         my $marcxml = XML::LibXML->new->parse_string($xml);
134         $marcxml->documentElement->setNamespace($MARC_NAMESPACE, "marc", 1 );
135         $marcxml->documentElement->setNamespace($MARC_NAMESPACE);
136         __remove_empty_marc_nodes($marcxml);
137         return $marcxml;
138 }
139
140 # remove empty control fields, subfields, and variable data fields, which
141 # can creep in via less-than-correct imported MARC records or issues
142 # with templates
143 sub __remove_empty_marc_nodes {
144         my $marcxml = shift;
145
146         __remove_if_childless($_) foreach $marcxml->documentElement->getElementsByTagNameNS($MARC_NAMESPACE, 'controlfield');
147         __remove_if_childless($_) foreach $marcxml->documentElement->getElementsByTagNameNS($MARC_NAMESPACE, 'subfield');
148         __remove_if_childless($_) foreach $marcxml->documentElement->getElementsByTagNameNS($MARC_NAMESPACE, 'datafield');
149 }
150
151 sub __remove_if_childless {
152         my $node = shift;
153         my @children = $node->childNodes();
154         my $has_nonblank_children = 0;
155         # can do this more concisely by requiring XML::LibXML >= 1.70 and using nonBlankChildNodes()
156         foreach my $node ($node->childNodes()) {
157                 if ($node->nodeType != XML::LibXML::XML_TEXT_NODE || $node->nodeValue !~ /^\s*$/) {
158                         $has_nonblank_children = 1;
159                         last;
160                 }
161         }
162         $node->parentNode->removeChild($node) unless $has_nonblank_children;
163 }
164
165 sub _find_tcn_info { 
166         my $editor              = shift;
167         my $xml                 = shift;
168         my $override    = shift;
169         my $existing_rec        = shift || 0;
170
171         # parse the XML
172         my $marcxml = __make_marc_doc($xml);
173
174         my $xpath = '//marc:controlfield[@tag="001"]';
175         my $tcn = $marcxml->documentElement->findvalue($xpath);
176         $logger->info("biblio import located 001 (tcn) value of $tcn");
177
178         $xpath = '//marc:controlfield[@tag="003"]';
179         my $tcn_source = $marcxml->documentElement->findvalue($xpath) || "System Local";
180
181         if(my $rec = _tcn_exists($editor, $tcn, $tcn_source, $existing_rec) ) {
182
183                 my $origtcn = $tcn;
184                 $tcn = find_free_tcn( $marcxml, $editor, $existing_rec );
185
186                 # if we're overriding, try to find a different TCN to use
187                 if( $override ) {
188
189          # XXX Create ALLOW_ALT_TCN permission check support 
190
191                         $logger->info("tcn value $tcn already exists, attempting to override");
192
193                         if(!$tcn) {
194                                 return ( 
195                                         undef, 
196                                         undef, 
197                                         undef,
198                                         OpenILS::Event->new(
199                                                 'OPEN_TCN_NOT_FOUND', 
200                                                         payload => $marcxml->toString())
201                                         );
202                         }
203
204                 } else {
205
206                         $logger->warn("tcn value $origtcn already exists in import/create");
207
208                         # otherwise, return event
209                         return ( 
210                                 undef, 
211                                 undef, 
212                                 undef,
213                                 OpenILS::Event->new( 
214                                         'TCN_EXISTS', payload => { 
215                                                 dup_record      => $rec, 
216                                                 tcn                     => $origtcn,
217                                                 new_tcn         => $tcn
218                                                 }
219                                         )
220                                 );
221                 }
222         }
223
224         return ($tcn, $tcn_source, $marcxml);
225 }
226
227 sub find_free_tcn {
228
229         my $marcxml = shift;
230         my $editor = shift;
231         my $existing_rec = shift;
232
233         my $add_901 = 0;
234
235         my $xpath = '//marc:datafield[@tag="901"]/marc:subfield[@code="a"]';
236         my ($tcn) = $marcxml->documentElement->findvalue($xpath) =~ /(\w+)\s*$/o;
237
238     if (!$tcn) {
239             $xpath = '//marc:datafield[@tag="039"]/marc:subfield[@code="a"]';
240             ($tcn) = $marcxml->documentElement->findvalue($xpath) =~ /(\w+)\s*$/o;
241     }
242
243         $xpath = '//marc:datafield[@tag="901"]/marc:subfield[@code="b"]';
244         my ($tcn_source) = $marcxml->documentElement->findvalue($xpath);
245     if (!$tcn_source) {
246             $xpath = '//marc:datafield[@tag="039"]/marc:subfield[@code="b"]';
247         $tcn_source = $marcxml->documentElement->findvalue($xpath) || "System Local";
248     }
249
250         if(_tcn_exists($editor, $tcn, $tcn_source, $existing_rec)) {
251                 $tcn = undef;
252         } else {
253                 $add_901++;
254         }
255
256
257         if(!$tcn) {
258                 $xpath = '//marc:datafield[@tag="020"]/marc:subfield[@code="a"]';
259                 ($tcn) = $marcxml->documentElement->findvalue($xpath) =~ /(\w+)\s*$/o;
260                 $tcn_source = "ISBN";
261                 if(_tcn_exists($editor, $tcn, $tcn_source, $existing_rec)) {$tcn = undef;}
262         }
263
264         if(!$tcn) { 
265                 $xpath = '//marc:datafield[@tag="022"]/marc:subfield[@code="a"]';
266                 ($tcn) = $marcxml->documentElement->findvalue($xpath) =~ /(\w+)\s*$/o;
267                 $tcn_source = "ISSN";
268                 if(_tcn_exists($editor, $tcn, $tcn_source, $existing_rec)) {$tcn = undef;}
269         }
270
271         if(!$tcn) {
272                 $xpath = '//marc:datafield[@tag="010"]';
273                 ($tcn) = $marcxml->documentElement->findvalue($xpath) =~ /(\w+)\s*$/o;
274                 $tcn_source = "LCCN";
275                 if(_tcn_exists($editor, $tcn, $tcn_source, $existing_rec)) {$tcn = undef;}
276         }
277
278         if(!$tcn) {
279                 $xpath = '//marc:datafield[@tag="035"]/marc:subfield[@code="a"]';
280                 ($tcn) = $marcxml->documentElement->findvalue($xpath) =~ /(\w+)\s*$/o;
281                 $tcn_source = "System Legacy";
282                 if(_tcn_exists($editor, $tcn, $tcn_source, $existing_rec)) {$tcn = undef;}
283
284                 if($tcn) {
285                         $marcxml->documentElement->removeChild(
286                                 $marcxml->documentElement->findnodes( '//marc:datafield[@tag="035"]' )
287                         );
288                 }
289         }
290
291         return undef unless $tcn;
292
293         if ($add_901) {
294                 my $df = $marcxml->createElementNS( 'http://www.loc.gov/MARC21/slim', 'datafield');
295                 $df->setAttribute( tag => '901' );
296                 $df->setAttribute( ind1 => ' ' );
297                 $df->setAttribute( ind2 => ' ' );
298                 $marcxml->documentElement->appendChild( $df );
299
300                 my $sfa = $marcxml->createElementNS( 'http://www.loc.gov/MARC21/slim', 'subfield');
301                 $sfa->setAttribute( code => 'a' );
302                 $sfa->appendChild( $marcxml->createTextNode( $tcn ) );
303                 $df->appendChild( $sfa );
304
305                 my $sfb = $marcxml->createElementNS( 'http://www.loc.gov/MARC21/slim', 'subfield');
306                 $sfb->setAttribute( code => 'b' );
307                 $sfb->appendChild( $marcxml->createTextNode( $tcn_source ) );
308                 $df->appendChild( $sfb );
309
310         if ($existing_rec) {
311                 my $sfc = $marcxml->createElementNS( 'http://www.loc.gov/MARC21/slim', 'subfield');
312                 $sfc->setAttribute( code => 'c' );
313                 $sfc->appendChild( $marcxml->createTextNode( $existing_rec ) );
314                     $df->appendChild( $sfb );
315         }
316         }
317
318         return $tcn;
319 }
320
321
322
323 sub _tcn_exists {
324         my $editor = shift;
325         my $tcn = shift;
326         my $source = shift;
327         my $existing_rec = shift || 0;
328
329         if(!$tcn) {return 0;}
330
331         $logger->debug("tcn_exists search for tcn $tcn and source $source and id $existing_rec");
332
333         # XXX why does the source matter?
334 #       my $req = $session->request(      
335 #               { tcn_value => $tcn, tcn_source => $source, deleted => 'f' } );
336
337     my $recs = $editor->search_biblio_record_entry(
338         {tcn_value => $tcn, deleted => 'f', id => {'!=' => $existing_rec}}, {idlist =>1});
339
340         if(@$recs) {
341                 $logger->debug("_tcn_exists is true for tcn : $tcn ($source)");
342                 return $recs->[0];
343         }
344
345         $logger->debug("_tcn_exists is false for tcn : $tcn ($source)");
346         return 0;
347 }
348
349
350 sub delete_rec {
351    my($class, $editor, $rec_id ) = @_;
352
353    my $rec = $editor->retrieve_biblio_record_entry($rec_id)
354       or return $editor->event;
355
356    return undef if $U->is_true($rec->deleted);
357    
358    $rec->deleted('t');
359    $rec->active('f');
360    $rec->editor( $editor->requestor->id );
361    $rec->edit_date('now');
362
363    # Set the leader/05 to indicate that the record has been deleted
364    my $marc = $rec->marc();
365    $marc =~ s{(<leader>.{5}).}{$1d};
366    $rec->marc($marc);
367
368    $editor->update_biblio_record_entry($rec) or return $editor->event;
369
370    return undef;
371 }
372
373
374 # ---------------------------------------------------------------------------
375 # returns true if the given title (id) has no un-deleted volumes or 
376 # copies attached.  If a context volume is defined, a record
377 # is considered empty only if the context volume is the only
378 # remaining volume on the record.  
379 # ---------------------------------------------------------------------------
380 sub title_is_empty {
381         my($class, $editor, $rid, $vol_id) = @_;
382
383         return 0 if $rid == OILS_PRECAT_RECORD;
384
385         my $cnlist = $editor->search_asset_call_number(
386                 { record => $rid, deleted => 'f' }, { idlist => 1 } );
387
388         return 1 unless @$cnlist; # no attached volumes
389     return 0 if @$cnlist > 1; # multiple attached volumes
390     return 0 unless $$cnlist[0] == $vol_id; # attached volume is not the context vol.
391
392     # see if the sole remaining context volume has any attached copies
393         for my $cn (@$cnlist) {
394                 my $copylist = $editor->search_asset_copy(
395                         [
396                                 { call_number => $cn, deleted => 'f' }, 
397                                 { limit => 1 },
398                         ], { idlist => 1 });
399                 return 0 if @$copylist; # false if we find any copies
400         }
401
402         return 1;
403 }
404 1;