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