moved marc edit/import functionaly out to an external module so it can be imported...
[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 OpenILS::Event;
9 my $U = 'OpenILS::Application::AppUtils';
10 my $MARC_NAMESPACE = 'http://www.loc.gov/MARC21/slim';
11
12
13 # ---------------------------------------------------------------------------
14 # Shared bib mangling code.  Do not publish methods from here.
15 # ---------------------------------------------------------------------------
16
17
18 sub biblio_record_replace_marc  {
19         my($class, $e, $recid, $newxml, $source, $fixtcn, $override) = @_;
20
21         my $rec = $e->retrieve_biblio_record_entry($recid)
22                 or return $e->die_event;
23
24     # See if there is a different record in the database that has our TCN value
25     # If we're not updating the TCN, all we care about it the marcdoc
26     # XXX should .update even bother with the tcn_info if it's not going to replace it?
27     # there is the potential for returning a TCN_EXISTS event, even though no replacement happens
28
29         my( $tcn, $tsource, $marcdoc, $evt);
30
31     if($fixtcn or $override) {
32
33             ($tcn, $tsource, $marcdoc, $evt) = 
34                     _find_tcn_info($e, $newxml, $override, $recid);
35
36             return $evt if $evt;
37
38                 $rec->tcn_value($tcn) if ($tcn);
39                 $rec->tcn_source($tsource);
40
41     } else {
42
43         $marcdoc = __make_marc_doc($newxml);
44     }
45
46
47         $rec->source(bib_source_from_name($source)) if $source;
48         $rec->editor($e->requestor->id);
49         $rec->edit_date('now');
50         $rec->marc( $U->entityize( $marcdoc->documentElement->toString ) );
51         $e->update_biblio_record_entry($rec) or return $e->die_event;
52
53     # we don't care about the result, just fire off the request
54     my $ses = OpenSRF::AppSession->create('open-ils.ingest');
55     $ses->request('open-ils.ingest.full.biblio.record', $recid);
56
57         return $rec;
58 }
59
60 sub biblio_record_xml_import {
61         my($class, $e, $xml, $source, $auto_tcn, $override) = @_;
62
63         my( $evt, $tcn, $tcn_source, $marcdoc );
64
65         if( $auto_tcn ) {
66                 # auto_tcn forces a blank TCN value so the DB will have to generate one for us
67                 $marcdoc = __make_marc_doc($xml);
68         } else {
69                 ( $tcn, $tcn_source, $marcdoc, $evt ) = _find_tcn_info($e, $xml, $override);
70                 return $evt if $evt;
71         }
72
73         $logger->info("user ".$e->requestor->id.
74                 " creating new biblio entry with tcn=$tcn and tcn_source $tcn_source");
75
76         my $record = Fieldmapper::biblio::record_entry->new;
77
78         $record->source(bib_source_from_name($source)) if $source;
79         $record->tcn_source($tcn_source);
80         $record->tcn_value($tcn) if ($tcn);
81         $record->creator($e->requestor->id);
82         $record->editor($e->requestor->id);
83         $record->create_date('now');
84         $record->edit_date('now');
85         $record->marc($U->entityize($marcdoc->documentElement->toString));
86
87     $record = $e->create_biblio_record_entry($record) or return $e->die_event;
88         $logger->info("marc create/import created new record ".$record->id);
89
90     # we don't care about the result, just fire off the request
91     my $ses = OpenSRF::AppSession->create('open-ils.ingest');
92     $ses->request('open-ils.ingest.full.biblio.record', $record->id);
93
94         return $record;
95 }
96
97 sub __make_marc_doc {
98         my $xml = shift;
99         my $marcxml = XML::LibXML->new->parse_string($xml);
100         $marcxml->documentElement->setNamespace($MARC_NAMESPACE, "marc", 1 );
101         $marcxml->documentElement->setNamespace($MARC_NAMESPACE);
102         return $marcxml;
103 }
104
105
106 sub _find_tcn_info { 
107         my $editor              = shift;
108         my $xml                 = shift;
109         my $override    = shift;
110         my $existing_rec        = shift || 0;
111
112         # parse the XML
113         my $marcxml = __make_marc_doc($xml);
114
115         my $xpath = '//marc:controlfield[@tag="001"]';
116         my $tcn = $marcxml->documentElement->findvalue($xpath);
117         $logger->info("biblio import located 001 (tcn) value of $tcn");
118
119         $xpath = '//marc:controlfield[@tag="003"]';
120         my $tcn_source = $marcxml->documentElement->findvalue($xpath) || "System Local";
121
122         if(my $rec = _tcn_exists($editor, $tcn, $tcn_source, $existing_rec) ) {
123
124                 my $origtcn = $tcn;
125                 $tcn = find_free_tcn( $marcxml, $editor, $existing_rec );
126
127                 # if we're overriding, try to find a different TCN to use
128                 if( $override ) {
129
130          # XXX Create ALLOW_ALT_TCN permission check support 
131
132                         $logger->info("tcn value $tcn already exists, attempting to override");
133
134                         if(!$tcn) {
135                                 return ( 
136                                         undef, 
137                                         undef, 
138                                         undef,
139                                         OpenILS::Event->new(
140                                                 'OPEN_TCN_NOT_FOUND', 
141                                                         payload => $marcxml->toString())
142                                         );
143                         }
144
145                 } else {
146
147                         $logger->warn("tcn value $origtcn already exists in import/create");
148
149                         # otherwise, return event
150                         return ( 
151                                 undef, 
152                                 undef, 
153                                 undef,
154                                 OpenILS::Event->new( 
155                                         'TCN_EXISTS', payload => { 
156                                                 dup_record      => $rec, 
157                                                 tcn                     => $origtcn,
158                                                 new_tcn         => $tcn
159                                                 }
160                                         )
161                                 );
162                 }
163         }
164
165         return ($tcn, $tcn_source, $marcxml);
166 }
167
168 sub find_free_tcn {
169
170         my $marcxml = shift;
171         my $editor = shift;
172         my $existing_rec = shift;
173
174         my $add_039 = 0;
175
176         my $xpath = '//marc:datafield[@tag="039"]/subfield[@code="a"]';
177         my ($tcn) = $marcxml->documentElement->findvalue($xpath) =~ /(\w+)\s*$/o;
178         $xpath = '//marc:datafield[@tag="039"]/subfield[@code="b"]';
179         my $tcn_source = $marcxml->documentElement->findvalue($xpath) || "System Local";
180
181         if(_tcn_exists($editor, $tcn, $tcn_source, $existing_rec)) {
182                 $tcn = undef;
183         } else {
184                 $add_039++;
185         }
186
187
188         if(!$tcn) {
189                 $xpath = '//marc:datafield[@tag="020"]/subfield[@code="a"]';
190                 ($tcn) = $marcxml->documentElement->findvalue($xpath) =~ /(\w+)\s*$/o;
191                 $tcn_source = "ISBN";
192                 if(_tcn_exists($editor, $tcn, $tcn_source, $existing_rec)) {$tcn = undef;}
193         }
194
195         if(!$tcn) { 
196                 $xpath = '//marc:datafield[@tag="022"]/subfield[@code="a"]';
197                 ($tcn) = $marcxml->documentElement->findvalue($xpath) =~ /(\w+)\s*$/o;
198                 $tcn_source = "ISSN";
199                 if(_tcn_exists($editor, $tcn, $tcn_source, $existing_rec)) {$tcn = undef;}
200         }
201
202         if(!$tcn) {
203                 $xpath = '//marc:datafield[@tag="010"]';
204                 ($tcn) = $marcxml->documentElement->findvalue($xpath) =~ /(\w+)\s*$/o;
205                 $tcn_source = "LCCN";
206                 if(_tcn_exists($editor, $tcn, $tcn_source, $existing_rec)) {$tcn = undef;}
207         }
208
209         if(!$tcn) {
210                 $xpath = '//marc:datafield[@tag="035"]/subfield[@code="a"]';
211                 ($tcn) = $marcxml->documentElement->findvalue($xpath) =~ /(\w+)\s*$/o;
212                 $tcn_source = "System Legacy";
213                 if(_tcn_exists($editor, $tcn, $tcn_source, $existing_rec)) {$tcn = undef;}
214
215                 if($tcn) {
216                         $marcxml->documentElement->removeChild(
217                                 $marcxml->documentElement->findnodes( '//datafield[@tag="035"]' )
218                         );
219                 }
220         }
221
222         return undef unless $tcn;
223
224         if ($add_039) {
225                 my $df = $marcxml->createElementNS( 'http://www.loc.gov/MARC21/slim', 'datafield');
226                 $df->setAttribute( tag => '039' );
227                 $df->setAttribute( ind1 => ' ' );
228                 $df->setAttribute( ind2 => ' ' );
229                 $marcxml->documentElement->appendChild( $df );
230
231                 my $sfa = $marcxml->createElementNS( 'http://www.loc.gov/MARC21/slim', 'subfield');
232                 $sfa->setAttribute( code => 'a' );
233                 $sfa->appendChild( $marcxml->createTextNode( $tcn ) );
234                 $df->appendChild( $sfa );
235
236                 my $sfb = $marcxml->createElementNS( 'http://www.loc.gov/MARC21/slim', 'subfield');
237                 $sfb->setAttribute( code => 'b' );
238                 $sfb->appendChild( $marcxml->createTextNode( $tcn_source ) );
239                 $df->appendChild( $sfb );
240         }
241
242         return $tcn;
243 }
244
245
246
247 sub _tcn_exists {
248         my $editor = shift;
249         my $tcn = shift;
250         my $source = shift;
251         my $existing_rec = shift || 0;
252
253         if(!$tcn) {return 0;}
254
255         $logger->debug("tcn_exists search for tcn $tcn and source $source and id $existing_rec");
256
257         # XXX why does the source matter?
258 #       my $req = $session->request(      
259 #               { tcn_value => $tcn, tcn_source => $source, deleted => 'f' } );
260
261     my $recs = $editor->search_biblio_record_entry(
262         {tcn_value => $tcn, deleted => 'f', id => {'!=' => $existing_rec}}, {idlist =>1});
263
264         if(@$recs) {
265                 $logger->debug("_tcn_exists is true for tcn : $tcn ($source)");
266                 return $recs->[0];
267         }
268
269         $logger->debug("_tcn_exists is false for tcn : $tcn ($source)");
270         return 0;
271 }
272
273