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