Bug 21395: Make perlcritic happy
[koha.git] / misc / migration_tools / bulkmarcimport.pl
1 #!/usr/bin/perl
2 # Import an iso2709 file into Koha 3
3
4 use Modern::Perl;
5 #use diagnostics;
6 BEGIN {
7     # find Koha's Perl modules
8     # test carefully before changing this
9     use FindBin;
10     eval { require "$FindBin::Bin/../kohalib.pl" };
11 }
12
13 # Koha modules used
14 use MARC::File::USMARC;
15 use MARC::File::XML;
16 use MARC::Record;
17 use MARC::Batch;
18 use MARC::Charset;
19
20 use Koha::Script;
21 use C4::Context;
22 use C4::Biblio;
23 use C4::Koha;
24 use C4::Debug;
25 use C4::Charset;
26 use C4::Items;
27 use C4::MarcModificationTemplates;
28
29 use YAML;
30 use Unicode::Normalize;
31 use Time::HiRes qw(gettimeofday);
32 use Getopt::Long;
33 use IO::File;
34 use Pod::Usage;
35
36 use Koha::Biblios;
37 use Koha::SearchEngine;
38 use Koha::SearchEngine::Search;
39
40 use open qw( :std :encoding(UTF-8) );
41 binmode( STDOUT, ":encoding(UTF-8)" );
42 my ( $input_marc_file, $number, $offset) = ('',0,0);
43 my ($version, $delete, $test_parameter, $skip_marc8_conversion, $char_encoding, $verbose, $commit, $fk_off,$format,$biblios,$authorities,$keepids,$match, $isbn_check, $logfile);
44 my ( $insert, $filters, $update, $all, $yamlfile, $authtypes, $append );
45 my $cleanisbn = 1;
46 my ($sourcetag,$sourcesubfield,$idmapfl, $dedup_barcode);
47 my $framework = '';
48 my $localcust;
49 my $marc_mod_template = '';
50 my $marc_mod_template_id = -1;
51
52 $|=1;
53
54 GetOptions(
55     'commit:f'    => \$commit,
56     'file:s'    => \$input_marc_file,
57     'n:f' => \$number,
58     'o|offset:f' => \$offset,
59     'h' => \$version,
60     'd' => \$delete,
61     't|test' => \$test_parameter,
62     's' => \$skip_marc8_conversion,
63     'c:s' => \$char_encoding,
64     'v:+' => \$verbose,
65     'fk' => \$fk_off,
66     'm:s' => \$format,
67     'l:s' => \$logfile,
68     'append' => \$append,
69     'k|keepids:s' => \$keepids,
70     'b|biblios' => \$biblios,
71     'a|authorities' => \$authorities,
72     'authtypes:s' => \$authtypes,
73     'filter=s@'     => \$filters,
74     'insert'        => \$insert,
75     'update'        => \$update,
76     'all'           => \$all,
77     'match=s@'    => \$match,
78     'i|isbn' => \$isbn_check,
79     'x:s' => \$sourcetag,
80     'y:s' => \$sourcesubfield,
81     'idmap:s' => \$idmapfl,
82     'cleanisbn!'     => \$cleanisbn,
83     'yaml:s'        => \$yamlfile,
84     'dedupbarcode' => \$dedup_barcode,
85     'framework=s' => \$framework,
86     'custom:s'    => \$localcust,
87     'marcmodtemplate:s' => \$marc_mod_template,
88 );
89 $biblios ||= !$authorities;
90 $insert  ||= !$update;
91 my $writemode = ($append) ? "a" : "w";
92
93 pod2usage( -msg => "\nYou must specify either --biblios or --authorities, not both.\n", -exitval ) if $biblios && $authorities;
94
95 if ($all) {
96     $insert = 1;
97     $update = 1;
98 }
99
100 if ($version || ($input_marc_file eq '')) {
101     pod2usage( -verbose => 2 );
102     exit;
103 }
104 if( $update && !( $match || $isbn_check ) ) {
105     warn "Using -update without -match or -isbn seems to be useless.\n";
106 }
107
108 if(defined $localcust) { #local customize module
109     if(!-e $localcust) {
110         $localcust= $localcust||'LocalChanges'; #default name
111         $localcust=~ s/^.*\/([^\/]+)$/$1/; #extract file name only
112         $localcust=~ s/\.pm$//;           #remove extension
113         my $fqcust= $FindBin::Bin."/$localcust.pm"; #try migration_tools dir
114         if(-e $fqcust) {
115             $localcust= $fqcust;
116         }
117         else {
118             print "WARNING: customize module $localcust.pm not found!\n";
119             exit 1;
120         }
121     }
122     require $localcust if $localcust;
123     $localcust=\&customize if $localcust;
124 }
125
126 if($marc_mod_template ne '') {
127     my @templates = GetModificationTemplates();
128     foreach my $this_template (@templates) {
129         if($this_template->{'name'} eq $marc_mod_template) {
130             if($marc_mod_template_id < 0) {
131                 $marc_mod_template_id = $this_template->{'template_id'};
132             } else {
133                 print "WARNING: MARC modification template name " .
134                 "'$marc_mod_template' matches multiple templates. " .
135                 "Please rename these templates\n";
136                 exit 1;
137             }
138         }
139     }
140     if($marc_mod_template_id < 0) {
141         die "Can't located MARC modification template '$marc_mod_template'\n";
142     } else {
143         print "Records will be modified using MARC modofication template: $marc_mod_template\n" if $verbose;
144     }
145 }
146
147 my $dbh = C4::Context->dbh;
148 my $heading_fields=get_heading_fields();
149
150 my $idmapfh;
151 if (defined $idmapfl) {
152   open($idmapfh, '>', $idmapfl) or die "cannot open $idmapfl \n";
153 }
154
155 if ((not defined $sourcesubfield) && (not defined $sourcetag)){
156   $sourcetag="910";
157   $sourcesubfield="a";
158 }
159
160
161 # Disable logging for the biblios and authorities import operation. It would unnecessarily
162 # slow the import
163 $ENV{OVERRIDE_SYSPREF_CataloguingLog} = 0;
164 $ENV{OVERRIDE_SYSPREF_AuthoritiesLog} = 0;
165
166 if ($fk_off) {
167         $dbh->do("SET FOREIGN_KEY_CHECKS = 0");
168 }
169
170
171 if ($delete) {
172         if ($biblios){
173         print "deleting biblios\n";
174         $dbh->do("DELETE FROM biblio");
175         $dbh->do("ALTER TABLE biblio AUTO_INCREMENT = 1");
176         $dbh->do("DELETE FROM biblioitems");
177         $dbh->do("ALTER TABLE biblioitems AUTO_INCREMENT = 1");
178         $dbh->do("DELETE FROM items");
179         $dbh->do("ALTER TABLE items AUTO_INCREMENT = 1");
180         }
181         else {
182         print "deleting authorities\n";
183         $dbh->do("truncate auth_header");
184         }
185     $dbh->do("truncate zebraqueue");
186 }
187
188
189
190 if ($test_parameter) {
191     print "TESTING MODE ONLY\n    DOING NOTHING\n===============\n";
192 }
193
194 my $marcFlavour = C4::Context->preference('marcflavour') || 'MARC21';
195
196 # The definition of $searcher must be before MARC::Batch->new
197 my $searcher = Koha::SearchEngine::Search->new(
198     {
199         index => (
200               $authorities
201             ? $Koha::SearchEngine::AUTHORITIES_INDEX
202             : $Koha::SearchEngine::BIBLIOS_INDEX
203         )
204     }
205 );
206
207 print "Characteristic MARC flavour: $marcFlavour\n" if $verbose;
208 my $starttime = gettimeofday;
209 my $batch;
210 my $fh = IO::File->new($input_marc_file); # don't let MARC::Batch open the file, as it applies the ':utf8' IO layer
211 if (defined $format && $format =~ /XML/i) {
212     # ugly hack follows -- MARC::File::XML, when used by MARC::Batch,
213     # appears to try to convert incoming XML records from MARC-8
214     # to UTF-8.  Setting the BinaryEncoding key turns that off
215     # TODO: see what happens to ISO-8859-1 XML files.
216     # TODO: determine if MARC::Batch can be fixed to handle
217     #       XML records properly -- it probably should be
218     #       be using a proper push or pull XML parser to
219     #       extract the records, not using regexes to look
220     #       for <record>.*</record>.
221     $MARC::File::XML::_load_args{BinaryEncoding} = 'utf-8';
222     my $recordformat= ($marcFlavour eq "MARC21"?"USMARC":uc($marcFlavour));
223 #UNIMARC Authorities have a different way to manage encoding than UNIMARC biblios.
224     $recordformat=$recordformat."AUTH" if ($authorities and $marcFlavour ne "MARC21");
225     $MARC::File::XML::_load_args{RecordFormat} = $recordformat;
226     $batch = MARC::Batch->new( 'XML', $fh );
227 } else {
228     $batch = MARC::Batch->new( 'USMARC', $fh );
229 }
230 $batch->warnings_off();
231 $batch->strict_off();
232 my $i=0;
233 my $commitnum = $commit ? $commit : 50;
234 my $yamlhash;
235
236 # Skip file offset
237 if ( $offset ) {
238     print "Skipping file offset: $offset records\n";
239     $batch->next() while ($offset--);
240 }
241
242 my ($tagid,$subfieldid);
243 if ($authorities){
244           $tagid='001';
245 }
246 else {
247    ( $tagid, $subfieldid ) =
248             GetMarcFromKohaField( "biblio.biblionumber" );
249         $tagid||="001";
250 }
251
252 # the SQL query to search on isbn
253 my $sth_isbn = $dbh->prepare("SELECT biblionumber,biblioitemnumber FROM biblioitems WHERE isbn=?");
254
255 $dbh->{AutoCommit} = 0;
256 my $loghandle;
257 if ($logfile){
258    $loghandle= IO::File->new($logfile, $writemode) ;
259    print $loghandle "id;operation;status\n";
260 }
261
262 RECORD: while (  ) {
263     my $record;
264     # get records
265     eval { $record = $batch->next() };
266     if ( $@ ) {
267         print "Bad MARC record $i: $@ skipped\n";
268         # FIXME - because MARC::Batch->next() combines grabbing the next
269         # blob and parsing it into one operation, a correctable condition
270         # such as a MARC-8 record claiming that it's UTF-8 can't be recovered
271         # from because we don't have access to the original blob.  Note
272         # that the staging import can deal with this condition (via
273         # C4::Charset::MarcToUTF8Record) because it doesn't use MARC::Batch.
274         next;
275     }
276     # skip if we get an empty record (that is MARC valid, but will result in AddBiblio failure
277     last unless ( $record );
278     $i++;
279     if( ($verbose//1)==1 ) { #no dot for verbose==2
280         print "." . ( $i % 100==0 ? "\n$i" : '' );
281     }
282
283     # transcode the record to UTF8 if needed & applicable.
284     if ($record->encoding() eq 'MARC-8' and not $skip_marc8_conversion) {
285         # FIXME update condition
286         my ($guessed_charset, $charset_errors);
287          ($record, $guessed_charset, $charset_errors) = MarcToUTF8Record($record, $marcFlavour.(($authorities and $marcFlavour ne "MARC21")?'AUTH':''));
288         if ($guessed_charset eq 'failed') {
289             warn "ERROR: failed to perform character conversion for record $i\n";
290             next RECORD;            
291         }
292     }
293     SetUTF8Flag($record);
294     if($marc_mod_template_id > 0) {
295     print "Modifying MARC\n" if $verbose;
296     ModifyRecordWithTemplate( $marc_mod_template_id, $record );
297     }
298     &$localcust($record) if $localcust;
299     my $isbn;
300     # remove trailing - in isbn (only for biblios, of course)
301     if( $biblios ) {
302         my $tag = $marcFlavour eq 'UNIMARC' ? '010' : '020';
303         my $field = $record->field($tag);
304         $isbn = $field && $field->subfield('a');
305         if ( $isbn && $cleanisbn ) {
306             $isbn =~ s/-//g;
307             $field->update('a' => $isbn);
308         }
309     }
310     my $id;
311     # search for duplicates (based on Local-number)
312     my $originalid;
313     $originalid = GetRecordId( $record, $tagid, $subfieldid );
314     if ($match) {
315         require C4::Search;
316         my $query = build_query( $match, $record );
317         my $server = ( $authorities ? 'authorityserver' : 'biblioserver' );
318         $debug && warn $query;
319         my ( $error, $results, $totalhits ) = $searcher->simple_search_compat( $query, 0, 3, [$server] );
320         # changed to warn so able to continue with one broken record
321         if ( defined $error ) {
322             warn "unable to search the database for duplicates : $error";
323             printlog( { id => $id || $originalid || $match, op => "match", status => "ERROR" } ) if ($logfile);
324             next RECORD;
325         }
326         $debug && warn "$query $server : $totalhits";
327         if ( $results && scalar(@$results) == 1 ) {
328             my $marcrecord = C4::Search::new_record_from_zebra( $server, $results->[0] );
329             SetUTF8Flag($marcrecord);
330             $id = GetRecordId( $marcrecord, $tagid, $subfieldid );
331             if ( $authorities && $marcFlavour ) {
332                 #Skip if authority in database is the same as the on in database
333                 if ( $marcrecord->field('005') && $record->field('005') &&
334                      $marcrecord->field('005')->data && $record->field('005')->data &&
335                      $marcrecord->field('005')->data >= $record->field('005')->data ) {
336                     if ($yamlfile) {
337                         $yamlhash->{$originalid}->{'authid'} = $id;
338
339                         # we recover all subfields of the heading authorities
340                         my @subfields;
341                         foreach my $field ( $marcrecord->field("2..") ) {
342                             push @subfields, map { ( $_->[0] =~ /[a-z]/ ? $_->[1] : () ) } $field->subfields();
343                         }
344                         $yamlhash->{$originalid}->{'subfields'} = \@subfields;
345                         $yamlhash->{$originalid}->{'updated'} = 0;
346                     }
347                     next;
348                 }
349             }
350         } elsif ( $results && scalar(@$results) > 1 ) {
351             $debug && warn "more than one match for $query";
352         } else {
353             $debug && warn "nomatch for $query";
354         }
355     }
356     if ($keepids && $originalid) {
357             my $storeidfield;
358             if ( length($keepids) == 3 ) {
359                 $storeidfield = MARC::Field->new( $keepids, $originalid );
360             } else {
361                 $storeidfield = MARC::Field->new( substr( $keepids, 0, 3 ), "", "", substr( $keepids, 3, 1 ), $originalid );
362             }
363             $record->insert_fields_ordered($storeidfield);
364             $record->delete_field( $record->field($tagid) );
365     }
366     foreach my $stringfilter (@$filters) {
367         if ( length($stringfilter) == 3 ) {
368             foreach my $field ( $record->field($stringfilter) ) {
369                 $record->delete_field($field);
370                 $debug && warn "removed : ", $field->as_string;
371             }
372         } elsif ($stringfilter =~ /([0-9]{3})([a-z0-9])(.*)/) {
373             my $removetag = $1;
374             my $removesubfield = $2;
375             my $removematch = $3;
376             if ( ( $removetag > "010" ) && $removesubfield ) {
377                 foreach my $field ( $record->field($removetag) ) {
378                     $field->delete_subfield( code => "$removesubfield", match => $removematch );
379                     $debug && warn "Potentially removed : ", $field->subfield($removesubfield);
380                 }
381             }
382         }
383     }
384     unless ($test_parameter) {
385         if ($authorities){
386             use C4::AuthoritiesMarc;
387             my $authtypecode=GuessAuthTypeCode($record, $heading_fields);
388             my $authid= ($id?$id:GuessAuthId($record));
389             if ($authid && GetAuthority($authid) && $update ){
390             ## Authority has an id and is in database : Replace
391                 eval { ( $authid ) = ModAuthority($authid,$record, $authtypecode) };
392                 if ($@){
393                     warn "Problem with authority $authid Cannot Modify";
394                                         printlog({id=>$originalid||$id||$authid, op=>"edit",status=>"ERROR"}) if ($logfile);
395                 }
396                                 else{
397                                         printlog({id=>$originalid||$id||$authid, op=>"edit",status=>"ok"}) if ($logfile);
398                                 }
399             }  
400             elsif (defined $authid) {
401             ## An authid is defined but no authority in database : add
402                 eval { ( $authid ) = AddAuthority($record,$authid, $authtypecode) };
403                 if ($@){
404                     warn "Problem with authority $authid Cannot Add ".$@;
405                                         printlog({id=>$originalid||$id||$authid, op=>"insert",status=>"ERROR"}) if ($logfile);
406                 }
407                                 else{
408                                         printlog({id=>$originalid||$id||$authid, op=>"insert",status=>"ok"}) if ($logfile);
409                                 }
410             }
411                 else {
412             ## True insert in database
413                 eval { ( $authid ) = AddAuthority($record,"", $authtypecode) };
414                 if ($@){
415                     warn "Problem with authority $authid Cannot Add".$@;
416                                         printlog({id=>$originalid||$id||$authid, op=>"insert",status=>"ERROR"}) if ($logfile);
417                 }
418                                 else{
419                                         printlog({id=>$originalid||$id||$authid, op=>"insert",status=>"ok"}) if ($logfile);
420                                 }
421                 }
422             if ($yamlfile) {
423             $yamlhash->{$originalid}->{'authid'} = $authid;
424             my @subfields;
425             foreach my $field ( $record->field("2..") ) {
426                 push @subfields, map { ( $_->[0] =~ /[a-z]/ ? $_->[1] : () ) } $field->subfields();
427             }
428             $yamlhash->{$originalid}->{'subfields'} = \@subfields;
429             $yamlhash->{$originalid}->{'updated'} = 1;
430             }
431         }
432         else {
433             my ( $biblionumber, $biblioitemnumber, $itemnumbers_ref, $errors_ref );
434             $biblionumber = $id;
435             # check for duplicate, based on ISBN (skip it if we already have found a duplicate with match parameter
436             if (!$biblionumber && $isbn_check && $isbn) {
437     #         warn "search ISBN : $isbn";
438                 $sth_isbn->execute($isbn);
439                 ($biblionumber,$biblioitemnumber) = $sth_isbn->fetchrow;
440             }
441                 if (defined $idmapfl) {
442                                 if ($sourcetag < "010"){
443                                         if ($record->field($sourcetag)){
444                                           my $source = $record->field($sourcetag)->data();
445                                           printf($idmapfh "%s|%s\n",$source,$biblionumber);
446                                         }
447                             } else {
448                                         my $source=$record->subfield($sourcetag,$sourcesubfield);
449                                         printf($idmapfh "%s|%s\n",$source,$biblionumber);
450                           }
451                         }
452                                         # create biblio, unless we already have it ( either match or isbn )
453             if ($biblionumber) {
454                 eval{
455                     $biblioitemnumber = Koha::Biblios->find( $biblionumber )->biblioitem->biblioitemnumber;
456                 };
457                 if ($update) {
458                     eval { ModBiblio( $record, $biblionumber, GetFrameworkCode($biblionumber) ) };
459                     if ($@) {
460                         warn "ERROR: Edit biblio $biblionumber failed: $@\n";
461                         printlog( { id => $id || $originalid || $biblionumber, op => "update", status => "ERROR" } ) if ($logfile);
462                         next RECORD;
463                     } else {
464                         printlog( { id => $id || $originalid || $biblionumber, op => "update", status => "ok" } ) if ($logfile);
465                     }
466                 } else {
467                     printlog( { id => $id || $originalid || $biblionumber, op => "insert", status => "warning : already in database" } ) if ($logfile);
468                 }
469             } else {
470                 if ($insert) {
471                     eval { ( $biblionumber, $biblioitemnumber ) = AddBiblio( $record, '', { defer_marc_save => 1 } ) };
472                     if ($@) {
473                         warn "ERROR: Adding biblio $biblionumber failed: $@\n";
474                         printlog( { id => $id || $originalid || $biblionumber, op => "insert", status => "ERROR" } ) if ($logfile);
475                         next RECORD;
476                     } else {
477                         printlog( { id => $id || $originalid || $biblionumber, op => "insert", status => "ok" } ) if ($logfile);
478                     }
479                 } else {
480                     warn "WARNING: Updating record ".($id||$originalid)." failed";
481                     printlog( { id => $id || $originalid || $biblionumber, op => "update", status => "warning : not in database" } ) if ($logfile);
482                     next RECORD;
483                 }
484             }
485             eval { ( $itemnumbers_ref, $errors_ref ) = AddItemBatchFromMarc( $record, $biblionumber, $biblioitemnumber, '' ); };
486             my $error_adding = $@;
487             # Work on a clone so that if there are real errors, we can maybe
488             # fix them up later.
489                         my $clone_record = $record->clone();
490             C4::Biblio::_strip_item_fields($clone_record, '');
491             # This sets the marc fields if there was an error, and also calls
492             # defer_marc_save.
493             ModBiblioMarc( $clone_record, $biblionumber, $framework );
494             if ( $error_adding ) {
495                 warn "ERROR: Adding items to bib $biblionumber failed: $error_adding";
496                                 printlog({id=>$id||$originalid||$biblionumber, op=>"insertitem",status=>"ERROR"}) if ($logfile);
497                 # if we failed because of an exception, assume that 
498                 # the MARC columns in biblioitems were not set.
499                 next RECORD;
500             }
501                         else{
502                                 printlog({id=>$id||$originalid||$biblionumber, op=>"insertitem",status=>"ok"}) if ($logfile);
503                         }
504             if ($dedup_barcode && grep { exists $_->{error_code} && $_->{error_code} eq 'duplicate_barcode' } @$errors_ref) {
505                 # Find the record called 'barcode'
506                 my ($tag, $sub) = C4::Biblio::GetMarcFromKohaField( 'items.barcode' );
507                 # Now remove any items that didn't have a duplicate_barcode error,
508                 # erase the barcodes on items that did, and re-add those items.
509                 my %dupes;
510                 foreach my $i (0 .. $#{$errors_ref}) {
511                     my $ref = $errors_ref->[$i];
512                     if ($ref && ($ref->{error_code} eq 'duplicate_barcode')) {
513                         $dupes{$ref->{item_sequence}} = 1;
514                         # Delete the error message because we're going to
515                         # retry this one.
516                         delete $errors_ref->[$i];
517                     }
518                 }
519                 my $seq = 0;
520                 foreach my $field ($record->field($tag)) {
521                     $seq++;
522                     if ($dupes{$seq}) {
523                         # Here we remove the barcode
524                         $field->delete_subfield(code => $sub);
525                     } else {
526                         # otherwise we delete the field because we don't want
527                         # two of them
528                         $record->delete_fields($field);
529                     }
530                 }
531                 # Now re-add the record as before, adding errors to the prev list
532                 my $more_errors;
533                 eval { ( $itemnumbers_ref, $more_errors ) = AddItemBatchFromMarc( $record, $biblionumber, $biblioitemnumber, '' ); };
534                 if ( $@ ) {
535                     warn "ERROR: Adding items to bib $biblionumber failed: $@\n";
536                     printlog({id=>$id||$originalid||$biblionumber, op=>"insertitem",status=>"ERROR"}) if ($logfile);
537                     # if we failed because of an exception, assume that
538                     # the MARC columns in biblioitems were not set.
539                     ModBiblioMarc( $record, $biblionumber, $framework );
540                     next RECORD;
541                 } else {
542                     printlog({id=>$id||$originalid||$biblionumber, op=>"insertitem",status=>"ok"}) if ($logfile);
543                 }
544                 push @$errors_ref, @{ $more_errors };
545             }
546             if ($#{ $errors_ref } > -1) {
547                 report_item_errors($biblionumber, $errors_ref);
548             }
549             $yamlhash->{$originalid} = $biblionumber if ($yamlfile);
550         }
551         $dbh->commit() if (0 == $i % $commitnum);
552     }
553     print $record->as_formatted()."\n" if ($verbose//0)==2;
554     last if $i == $number;
555 }
556 $dbh->commit();
557 $dbh->{AutoCommit} = 1;
558
559
560 if ($fk_off) {
561         $dbh->do("SET FOREIGN_KEY_CHECKS = 1");
562 }
563
564 # Restore CataloguingLog and AuthoritiesLog
565 delete $ENV{OVERRIDE_SYSPREF_CataloguingLog};
566 delete $ENV{OVERRIDE_SYSPREF_AuthoritiesLog};
567
568 my $timeneeded = gettimeofday - $starttime;
569 print "\n$i MARC records done in $timeneeded seconds\n";
570 if ($logfile){
571   print $loghandle "file : $input_marc_file\n";
572   print $loghandle "$i MARC records done in $timeneeded seconds\n";
573   $loghandle->close;
574 }
575 if ($yamlfile) {
576     open my $yamlfileout, q{>}, "$yamlfile" or die "cannot open $yamlfile \n";
577     print $yamlfileout Dump($yamlhash);
578 }
579 exit 0;
580
581 sub GetRecordId{
582         my $marcrecord=shift;
583         my $tag=shift;
584         my $subfield=shift;
585         my $id;
586         if ($tag lt "010"){
587                 return $marcrecord->field($tag)->data() if $marcrecord->field($tag);
588         } 
589         elsif ($subfield){
590                 if ($marcrecord->field($tag)){
591                         return $marcrecord->subfield($tag,$subfield);
592                 }
593         }
594         return $id;
595 }
596 sub build_query {
597         my $match = shift;
598         my $record=shift;
599         my @searchstrings;
600         foreach my $matchingpoint (@$match){
601           my $string = build_simplequery($matchingpoint,$record);
602           push @searchstrings,$string if (length($string)>0);
603         }
604     my $op = 'and';
605     return join(" $op ",@searchstrings);
606 }
607 sub build_simplequery {
608         my $element=shift;
609         my $record=shift;
610     my @searchstrings;
611     my ($index,$recorddata)=split /,/,$element;
612     if ($recorddata=~/(\d{3})(.*)/) {
613         my ($tag,$subfields) =($1,$2);
614         foreach my $field ($record->field($tag)){
615                   if (length($field->as_string("$subfields"))>0){
616               push @searchstrings,"$index:\"".$field->as_string("$subfields")."\"";
617                   }
618         }
619     }
620     my $op = 'and';
621     return join(" $op ",@searchstrings);
622 }
623 sub report_item_errors {
624     my $biblionumber = shift;
625     my $errors_ref = shift;
626
627     foreach my $error (@{ $errors_ref }) {
628         next if !$error;
629         my $msg = "Item not added (bib $biblionumber, item tag #$error->{'item_sequence'}, barcode $error->{'item_barcode'}): ";
630         my $error_code = $error->{'error_code'};
631         $error_code =~ s/_/ /g;
632         $msg .= "$error_code $error->{'error_information'}";
633         print $msg, "\n";
634     }
635 }
636 sub printlog{
637         my $logelements=shift;
638     print $loghandle join( ";", map { defined $_ ? $_ : "" } @$logelements{qw<id op status>} ), "\n";
639 }
640 sub get_heading_fields{
641     my $headingfields;
642     if ($authtypes){
643         $headingfields=YAML::LoadFile($authtypes);
644         $headingfields={C4::Context->preference('marcflavour')=>$headingfields};
645         $debug && warn YAML::Dump($headingfields);
646     }
647     unless ($headingfields){
648         $headingfields=$dbh->selectall_hashref("SELECT auth_tag_to_report, authtypecode from auth_types",'auth_tag_to_report',{Slice=>{}});
649         $headingfields={C4::Context->preference('marcflavour')=>$headingfields};
650     }
651     return $headingfields;
652 }
653
654 =head1 NAME
655
656 bulkmarcimport.pl - Import bibliographic/authority records into Koha
657
658 =head1 USAGE
659
660  $ export KOHA_CONF=/etc/koha.conf
661  $ perl misc/migration_tools/bulkmarcimport.pl -d -commit 1000 \\
662     -file /home/jmf/koha.mrc -n 3000
663
664 =head1 WARNING
665
666 Don't use this script before you've entered and checked your MARC parameters
667 tables twice (or more!). Otherwise, the import won't work correctly and you
668 will get invalid data.
669
670 =head1 DESCRIPTION
671
672 =over
673
674 =item  B<-h>
675
676 This version/help screen
677
678 =item B<-b, -biblios>
679
680 Type of import: bibliographic records
681
682 =item B<-a, -authorities>
683
684 Type of import: authority records
685
686 =item B<-file>=I<FILE>
687
688 The I<FILE> to import
689
690 =item  B<-v>
691
692 Verbose mode. 1 means "some infos", 2 means "MARC dumping"
693
694 =item B<-fk>
695
696 Turn off foreign key checks during import.
697
698 =item B<-n>=I<NUMBER>
699
700 The I<NUMBER> of records to import. If missing, all the file is imported
701
702 =item B<-o, -offset>=I<NUMBER>
703
704 File offset before importing, ie I<NUMBER> of records to skip.
705
706 =item B<-commit>=I<NUMBER>
707
708 The I<NUMBER> of records to wait before performing a 'commit' operation
709
710 =item B<-l>
711
712 File logs actions done for each record and their status into file
713
714 =item B<-append>
715
716 If specified, data will be appended to the logfile. If not, the logfile will be erased for each execution.
717
718 =item B<-t, -test>
719
720 Test mode: parses the file, saying what it would do, but doing nothing.
721
722 =item B<-s>
723
724 Skip automatic conversion of MARC-8 to UTF-8.  This option is provided for
725 debugging.
726
727 =item B<-c>=I<CHARACTERISTIC>
728
729 The I<CHARACTERISTIC> MARC flavour. At the moment, only I<MARC21> and
730 I<UNIMARC> are supported. MARC21 by default.
731
732 =item B<-d>
733
734 Delete EVERYTHING related to biblio in koha-DB before import. Tables: biblio,
735 biblioitems, items
736
737 =item B<-m>=I<FORMAT>
738
739 Input file I<FORMAT>: I<MARCXML> or I<ISO2709> (defaults to ISO2709)
740
741 =item B<-authtypes>
742
743 file yamlfile with authoritiesTypes and distinguishable record field in order
744 to store the correct authtype
745
746 =item B<-yaml>
747
748 yaml file  format a yaml file with ids
749
750 =item B<-filter>
751
752 list of fields that will not be imported. Can be any from 000 to 999 or field,
753 subfield and subfield's matching value such as 200avalue
754
755 =item B<-insert>
756
757 if set, only insert when possible
758
759 =item B<-update>
760
761 if set, only updates (any biblio should have a matching record)
762
763 =item B<-all>
764
765 if set, do whatever is required
766
767 =item B<-k, -keepids>=<FIELD>
768
769 Field store ids in I<FIELD> (useful for authorities, where 001 contains the
770 authid for Koha, that can contain a very valuable info for authorities coming
771 from LOC or BNF. useless for biblios probably)
772
773 =item B<-match>=<FIELD>
774
775 I<FIELD> matchindex,fieldtomatch matchpoint to use to deduplicate fieldtomatch
776 can be either 001 to 999 or field and list of subfields as such 100abcde
777
778 =item B<-i,-isbn>
779
780 If set, a search will be done on isbn, and, if the same isbn is found, the
781 biblio is not added. It's another method to deduplicate.  B<-match> & B<-isbn>
782 can be both set.
783
784 =item B<-cleanisbn>
785
786 Clean ISBN fields from entering biblio records, ie removes hyphens. By default,
787 ISBN are cleaned. --nocleanisbn will keep ISBN unchanged.
788
789 =item B<-x>=I<TAG>
790
791 Source bib I<TAG> for reporting the source bib number
792
793 =item B<-y>=I<SUBFIELD>
794
795 Source I<SUBFIELD> for reporting the source bib number
796
797 =item B<-idmap>=I<FILE>
798
799 I<FILE> for the koha bib and source id
800
801 =item B<-keepids>
802
803 Store ids in 009 (useful for authorities, where 001 contains the authid for
804 Koha, that can contain a very valuable info for authorities coming from LOC or
805 BNF. useless for biblios probably)
806
807 =item B<-dedupbarcode>
808
809 If set, whenever a duplicate barcode is detected, it is removed and the attempt
810 to add the record is retried, thereby giving the record a blank barcode. This
811 is useful when something has set barcodes to be a biblio ID, or similar
812 (usually other software.)
813
814 =item B<-framework>
815
816 This is the code for the framework that the requested records will have attached
817 to them when they are created. If not specified, then the default framework
818 will be used.
819
820 =item B<-custom>=I<MODULE>
821
822 This parameter allows you to use a local module with a customize subroutine
823 that is called for each MARC record.
824 If no filename is passed, LocalChanges.pm is assumed to be in the
825 migration_tools subdirectory. You may pass an absolute file name or a file name
826 from the migration_tools directory.
827
828 =item B<-marcmodtemplate>=I<TEMPLATE>
829
830 This parameter allows you to specify the name of an existing MARC
831 modification template to apply as the MARC records are imported (these
832 templates are created in the "MARC modification templates" tool in Koha).
833 If not specified, no MARC modification templates are used (default).
834
835 =back
836
837 =cut
838