Bug 13530: Typo in bulkmarcimport
[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 unnecesarily
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         die "unable to search the database for duplicates : $error" if ( defined $error );
273         $debug && warn "$query $server : $totalhits";
274         if ( $results && scalar(@$results) == 1 ) {
275             my $marcrecord = C4::Search::new_record_from_zebra( $server, $results->[0] );
276             SetUTF8Flag($marcrecord);
277             $id = GetRecordId( $marcrecord, $tagid, $subfieldid );
278             if ( $authorities && $marcFlavour ) {
279                 #Skip if authority in database is the same as the on in database
280                 if ( $marcrecord->field('005') && $record->field('005') &&
281                      $marcrecord->field('005')->data && $record->field('005')->data &&
282                      $marcrecord->field('005')->data >= $record->field('005')->data ) {
283                     if ($yamlfile) {
284                         $yamlhash->{$originalid}->{'authid'} = $id;
285
286                         # we recover all subfields of the heading authorities
287                         my @subfields;
288                         foreach my $field ( $marcrecord->field("2..") ) {
289                             push @subfields, map { ( $_->[0] =~ /[a-z]/ ? $_->[1] : () ) } $field->subfields();
290                         }
291                         $yamlhash->{$originalid}->{'subfields'} = \@subfields;
292                     }
293                     next;
294                 }
295             }
296         } elsif ( $results && scalar(@$results) > 1 ) {
297             $debug && warn "more than one match for $query";
298         } else {
299             $debug && warn "nomatch for $query";
300         }
301     }
302     if ($keepids && $originalid) {
303             my $storeidfield;
304             if ( length($keepids) == 3 ) {
305                 $storeidfield = MARC::Field->new( $keepids, $originalid );
306             } else {
307                 $storeidfield = MARC::Field->new( substr( $keepids, 0, 3 ), "", "", substr( $keepids, 3, 1 ), $originalid );
308             }
309             $record->insert_fields_ordered($storeidfield);
310             $record->delete_field( $record->field($tagid) );
311     }
312     foreach my $stringfilter (@$filters) {
313         if ( length($stringfilter) == 3 ) {
314             foreach my $field ( $record->field($stringfilter) ) {
315                 $record->delete_field($field);
316                 $debug && warn "removed : ", $field->as_string;
317             }
318         } elsif ($stringfilter =~ /([0-9]{3})([a-z0-9])(.*)/) {
319             my $removetag = $1;
320             my $removesubfield = $2;
321             my $removematch = $3;
322             if ( ( $removetag > "010" ) && $removesubfield ) {
323                 foreach my $field ( $record->field($removetag) ) {
324                     $field->delete_subfield( code => "$removesubfield", match => $removematch );
325                     $debug && warn "Potentially removed : ", $field->subfield($removesubfield);
326                 }
327             }
328         }
329     }
330     unless ($test_parameter) {
331         if ($authorities){
332             use C4::AuthoritiesMarc;
333             my $authtypecode=GuessAuthTypeCode($record, $heading_fields);
334             my $authid= ($id?$id:GuessAuthId($record));
335             if ($authid && GetAuthority($authid) && $update ){
336             ## Authority has an id and is in database : Replace
337                 eval { ( $authid ) = ModAuthority($authid,$record, $authtypecode) };
338                 if ($@){
339                     warn "Problem with authority $authid Cannot Modify";
340                                         printlog({id=>$originalid||$id||$authid, op=>"edit",status=>"ERROR"}) if ($logfile);
341                 }
342                                 else{
343                                         printlog({id=>$originalid||$id||$authid, op=>"edit",status=>"ok"}) if ($logfile);
344                                 }
345             }  
346             elsif (defined $authid) {
347             ## An authid is defined but no authority in database : add
348                 eval { ( $authid ) = AddAuthority($record,$authid, $authtypecode) };
349                 if ($@){
350                     warn "Problem with authority $authid Cannot Add ".$@;
351                                         printlog({id=>$originalid||$id||$authid, op=>"insert",status=>"ERROR"}) if ($logfile);
352                 }
353                                 else{
354                                         printlog({id=>$originalid||$id||$authid, op=>"insert",status=>"ok"}) if ($logfile);
355                                 }
356             }
357                 else {
358             ## True insert in database
359                 eval { ( $authid ) = AddAuthority($record,"", $authtypecode) };
360                 if ($@){
361                     warn "Problem with authority $authid Cannot Add".$@;
362                                         printlog({id=>$originalid||$id||$authid, op=>"insert",status=>"ERROR"}) if ($logfile);
363                 }
364                                 else{
365                                         printlog({id=>$originalid||$id||$authid, op=>"insert",status=>"ok"}) if ($logfile);
366                                 }
367                 }
368             if ($yamlfile) {
369             $yamlhash->{$originalid}->{'authid'} = $authid;
370             my @subfields;
371             foreach my $field ( $record->field("2..") ) {
372                 push @subfields, map { ( $_->[0] =~ /[a-z]/ ? $_->[1] : () ) } $field->subfields();
373             }
374             $yamlhash->{$originalid}->{'subfields'} = \@subfields;
375             }
376         }
377         else {
378             my ( $biblionumber, $biblioitemnumber, $itemnumbers_ref, $errors_ref );
379             $biblionumber = $id;
380             # check for duplicate, based on ISBN (skip it if we already have found a duplicate with match parameter
381             if (!$biblionumber && $isbn_check && $isbn) {
382     #         warn "search ISBN : $isbn";
383                 $sth_isbn->execute($isbn);
384                 ($biblionumber,$biblioitemnumber) = $sth_isbn->fetchrow;
385             }
386                 if (defined $idmapfl) {
387                                 if ($sourcetag < "010"){
388                                         if ($record->field($sourcetag)){
389                                           my $source = $record->field($sourcetag)->data();
390                                           printf(IDMAP "%s|%s\n",$source,$biblionumber);
391                                         }
392                             } else {
393                                         my $source=$record->subfield($sourcetag,$sourcesubfield);
394                                         printf(IDMAP "%s|%s\n",$source,$biblionumber);
395                           }
396                         }
397                                         # create biblio, unless we already have it ( either match or isbn )
398             if ($biblionumber) {
399                 eval{$biblioitemnumber=GetBiblioData($biblionumber)->{biblioitemnumber};};
400                 if ($update) {
401                     eval { ( $biblionumber, $biblioitemnumber ) = ModBiblio( $record, $biblionumber, GetFrameworkCode($biblionumber) ) };
402                     if ($@) {
403                         warn "ERROR: Edit biblio $biblionumber failed: $@\n";
404                         printlog( { id => $id || $originalid || $biblionumber, op => "update", status => "ERROR" } ) if ($logfile);
405                         next RECORD;
406                     } else {
407                         printlog( { id => $id || $originalid || $biblionumber, op => "update", status => "ok" } ) if ($logfile);
408                     }
409                 } else {
410                     printlog( { id => $id || $originalid || $biblionumber, op => "insert", status => "warning : already in database" } ) if ($logfile);
411                 }
412             } else {
413                 if ($insert) {
414                     eval { ( $biblionumber, $biblioitemnumber ) = AddBiblio( $record, '', { defer_marc_save => 1 } ) };
415                     if ($@) {
416                         warn "ERROR: Adding biblio $biblionumber failed: $@\n";
417                         printlog( { id => $id || $originalid || $biblionumber, op => "insert", status => "ERROR" } ) if ($logfile);
418                         next RECORD;
419                     } else {
420                         printlog( { id => $id || $originalid || $biblionumber, op => "insert", status => "ok" } ) if ($logfile);
421                     }
422                 } else {
423                     printlog( { id => $id || $originalid || $biblionumber, op => "update", status => "warning : not in database" } ) if ($logfile);
424                 }
425             }
426             eval { ( $itemnumbers_ref, $errors_ref ) = AddItemBatchFromMarc( $record, $biblionumber, $biblioitemnumber, '' ); };
427             my $error_adding = $@;
428             # Work on a clone so that if there are real errors, we can maybe
429             # fix them up later.
430                         my $clone_record = $record->clone();
431             C4::Biblio::_strip_item_fields($clone_record, '');
432             # This sets the marc fields if there was an error, and also calls
433             # defer_marc_save.
434             ModBiblioMarc( $clone_record, $biblionumber, $framework );
435             if ( $error_adding ) {
436                 warn "ERROR: Adding items to bib $biblionumber failed: $error_adding";
437                                 printlog({id=>$id||$originalid||$biblionumber, op=>"insertitem",status=>"ERROR"}) if ($logfile);
438                 # if we failed because of an exception, assume that 
439                 # the MARC columns in biblioitems were not set.
440                 next RECORD;
441             }
442                         else{
443                                 printlog({id=>$id||$originalid||$biblionumber, op=>"insert",status=>"ok"}) if ($logfile);
444                         }
445             if ($dedup_barcode && grep { exists $_->{error_code} && $_->{error_code} eq 'duplicate_barcode' } @$errors_ref) {
446                 # Find the record called 'barcode'
447                 my ($tag, $sub) = C4::Biblio::GetMarcFromKohaField('items.barcode', $framework);
448                 # Now remove any items that didn't have a duplicate_barcode error,
449                 # erase the barcodes on items that did, and re-add those items.
450                 my %dupes;
451                 foreach my $i (0 .. $#{$errors_ref}) {
452                     my $ref = $errors_ref->[$i];
453                     if ($ref && ($ref->{error_code} eq 'duplicate_barcode')) {
454                         $dupes{$ref->{item_sequence}} = 1;
455                         # Delete the error message because we're going to
456                         # retry this one.
457                         delete $errors_ref->[$i];
458                     }
459                 }
460                 my $seq = 0;
461                 foreach my $field ($record->field($tag)) {
462                     $seq++;
463                     if ($dupes{$seq}) {
464                         # Here we remove the barcode
465                         $field->delete_subfield(code => $sub);
466                     } else {
467                         # otherwise we delete the field because we don't want
468                         # two of them
469                         $record->delete_fields($field);
470                     }
471                 }
472                 # Now re-add the record as before, adding errors to the prev list
473                 my $more_errors;
474                 eval { ( $itemnumbers_ref, $more_errors ) = AddItemBatchFromMarc( $record, $biblionumber, $biblioitemnumber, '' ); };
475                 if ( $@ ) {
476                     warn "ERROR: Adding items to bib $biblionumber failed: $@\n";
477                     printlog({id=>$id||$originalid||$biblionumber, op=>"insertitem",status=>"ERROR"}) if ($logfile);
478                     # if we failed because of an exception, assume that
479                     # the MARC columns in biblioitems were not set.
480                     ModBiblioMarc( $record, $biblionumber, $framework );
481                     next RECORD;
482                 } else {
483                     printlog({id=>$id||$originalid||$biblionumber, op=>"insert",status=>"ok"}) if ($logfile);
484                 }
485                 push @$errors_ref, @{ $more_errors };
486             }
487             if ($#{ $errors_ref } > -1) {
488                 report_item_errors($biblionumber, $errors_ref);
489             }
490             $yamlhash->{$originalid} = $biblionumber if ($yamlfile);
491         }
492         $dbh->commit() if (0 == $i % $commitnum);
493     }
494     print $record->as_formatted()."\n" if ($verbose//0)==2;
495     last if $i == $number;
496 }
497 $dbh->commit();
498 $dbh->{AutoCommit} = 1;
499
500
501 if ($fk_off) {
502         $dbh->do("SET FOREIGN_KEY_CHECKS = 1");
503 }
504
505 # Restore CataloguingLog
506 C4::Context->set_preference( 'CataloguingLog', $CataloguingLog );
507 # Restore AuthoritiesLog
508 C4::Context->set_preference( 'AuthoritiesLog', $AuthoritiesLog );
509
510 my $timeneeded = gettimeofday - $starttime;
511 print "\n$i MARC records done in $timeneeded seconds\n";
512 if ($logfile){
513   print $loghandle "file : $input_marc_file\n";
514   print $loghandle "$i MARC records done in $timeneeded seconds\n";
515   $loghandle->close;
516 }
517 if ($yamlfile) {
518     open my $yamlfileout, q{>}, "$yamlfile" or die "cannot open $yamlfile \n";
519     print $yamlfileout Dump($yamlhash);
520 }
521 exit 0;
522
523 sub GetRecordId{
524         my $marcrecord=shift;
525         my $tag=shift;
526         my $subfield=shift;
527         my $id;
528         if ($tag lt "010"){
529                 return $marcrecord->field($tag)->data() if $marcrecord->field($tag);
530         } 
531         elsif ($subfield){
532                 if ($marcrecord->field($tag)){
533                         return $marcrecord->subfield($tag,$subfield);
534                 }
535         }
536         return $id;
537 }
538 sub build_query {
539         my $match = shift;
540         my $record=shift;
541         my @searchstrings;
542         foreach my $matchingpoint (@$match){
543           my $string = build_simplequery($matchingpoint,$record);
544           push @searchstrings,$string if (length($string)>0);
545         }
546     my $QParser;
547     $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser'));
548     my $op;
549     if ($QParser) {
550         $op = '&&';
551     } else {
552         $op = 'and';
553     }
554     return join(" $op ",@searchstrings);
555 }
556 sub build_simplequery {
557         my $element=shift;
558         my $record=shift;
559     my @searchstrings;
560     my ($index,$recorddata)=split /,/,$element;
561     if ($recorddata=~/(\d{3})(.*)/) {
562         my ($tag,$subfields) =($1,$2);
563         foreach my $field ($record->field($tag)){
564                   if (length($field->as_string("$subfields"))>0){
565               push @searchstrings,"$index:\"".$field->as_string("$subfields")."\"";
566                   }
567         }
568     }
569     my $QParser;
570     $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser'));
571     my $op;
572     if ($QParser) {
573         $op = '&&';
574     } else {
575         $op = 'and';
576     }
577     return join(" $op ",@searchstrings);
578 }
579 sub report_item_errors {
580     my $biblionumber = shift;
581     my $errors_ref = shift;
582
583     foreach my $error (@{ $errors_ref }) {
584         next if !$error;
585         my $msg = "Item not added (bib $biblionumber, item tag #$error->{'item_sequence'}, barcode $error->{'item_barcode'}): ";
586         my $error_code = $error->{'error_code'};
587         $error_code =~ s/_/ /g;
588         $msg .= "$error_code $error->{'error_information'}";
589         print $msg, "\n";
590     }
591 }
592 sub printlog{
593         my $logelements=shift;
594     print $loghandle join( ";", map { defined $_ ? $_ : "" } @$logelements{qw<id op status>} ), "\n";
595 }
596 sub get_heading_fields{
597     my $headingfields;
598     if ($authtypes){
599         $headingfields=YAML::LoadFile($authtypes);
600         $headingfields={C4::Context->preference('marcflavour')=>$headingfields};
601         $debug && warn YAML::Dump($headingfields);
602     }
603     unless ($headingfields){
604         $headingfields=$dbh->selectall_hashref("SELECT auth_tag_to_report, authtypecode from auth_types",'auth_tag_to_report',{Slice=>{}});
605         $headingfields={C4::Context->preference('marcflavour')=>$headingfields};
606     }
607     return $headingfields;
608 }
609
610 =head1 NAME
611
612 bulkmarcimport.pl - Import bibliographic/authority records into Koha
613
614 =head1 USAGE
615
616  $ export KOHA_CONF=/etc/koha.conf
617  $ perl misc/migration_tools/bulkmarcimport.pl -d -commit 1000 \\
618     -file /home/jmf/koha.mrc -n 3000
619
620 =head1 WARNING
621
622 Don't use this script before you've entered and checked your MARC parameters
623 tables twice (or more!). Otherwise, the import won't work correctly and you
624 will get invalid data.
625
626 =head1 DESCRIPTION
627
628 =over
629
630 =item  B<-h>
631
632 This version/help screen
633
634 =item B<-b, -biblios>
635
636 Type of import: bibliographic records
637
638 =item B<-a, -authorities>
639
640 Type of import: authority records
641
642 =item B<-file>=I<FILE>
643
644 The I<FILE> to import
645
646 =item  B<-v>
647
648 Verbose mode. 1 means "some infos", 2 means "MARC dumping"
649
650 =item B<-fk>
651
652 Turn off foreign key checks during import.
653
654 =item B<-n>=I<NUMBER>
655
656 The I<NUMBER> of records to import. If missing, all the file is imported
657
658 =item B<-o, -offset>=I<NUMBER>
659
660 File offset before importing, ie I<NUMBER> of records to skip.
661
662 =item B<-commit>=I<NUMBER>
663
664 The I<NUMBER> of records to wait before performing a 'commit' operation
665
666 =item B<-l>
667
668 File logs actions done for each record and their status into file
669
670 =item B<-append>
671
672 If specified, data will be appended to the logfile. If not, the logfile will be erased for each execution.
673
674 =item B<-t, -test>
675
676 Test mode: parses the file, saying what he would do, but doing nothing.
677
678 =item B<-s>
679
680 Skip automatic conversion of MARC-8 to UTF-8.  This option is provided for
681 debugging.
682
683 =item B<-c>=I<CHARACTERISTIC>
684
685 The I<CHARACTERISTIC> MARC flavour. At the moment, only I<MARC21> and
686 I<UNIMARC> are supported. MARC21 by default.
687
688 =item B<-d>
689
690 Delete EVERYTHING related to biblio in koha-DB before import. Tables: biblio,
691 biblioitems, items
692
693 =item B<-m>=I<FORMAT>
694
695 Input file I<FORMAT>: I<MARCXML> or I<ISO2709> (defaults to ISO2709)
696
697 =item B<-authtypes>
698
699 file yamlfile with authoritiesTypes and distinguishable record field in order
700 to store the correct authtype
701
702 =item B<-yaml>
703
704 yaml file  format a yaml file with ids
705
706 =item B<-filter>
707
708 list of fields that will not be imported. Can be any from 000 to 999 or field,
709 subfield and subfield's matching value such as 200avalue
710
711 =item B<-insert>
712
713 if set, only insert when possible
714
715 =item B<-update>
716
717 if set, only updates (any biblio should have a matching record)
718
719 =item B<-all>
720
721 if set, do whatever is required
722
723 =item B<-k, -keepids>=<FIELD>
724
725 Field store ids in I<FIELD> (usefull for authorities, where 001 contains the
726 authid for Koha, that can contain a very valuable info for authorities coming
727 from LOC or BNF. useless for biblios probably)
728
729 =item B<-match>=<FIELD>
730
731 I<FIELD> matchindex,fieldtomatch matchpoint to use to deduplicate fieldtomatch
732 can be either 001 to 999 or field and list of subfields as such 100abcde
733
734 =item B<-i,-isbn>
735
736 If set, a search will be done on isbn, and, if the same isbn is found, the
737 biblio is not added. It's another method to deduplicate.  B<-match> & B<-isbn>
738 can be both set.
739
740 =item B<-cleanisbn>
741
742 Clean ISBN fields from entering biblio records, ie removes hyphens. By default,
743 ISBN are cleaned. --nocleanisbn will keep ISBN unchanged.
744
745 =item B<-x>=I<TAG>
746
747 Source bib I<TAG> for reporting the source bib number
748
749 =item B<-y>=I<SUBFIELD>
750
751 Source I<SUBFIELD> for reporting the source bib number
752
753 =item B<-idmap>=I<FILE>
754
755 I<FILE> for the koha bib and source id
756
757 =item B<-keepids>
758
759 Store ids in 009 (usefull for authorities, where 001 contains the authid for
760 Koha, that can contain a very valuable info for authorities coming from LOC or
761 BNF. useless for biblios probably)
762
763 =item B<-dedupbarcode>
764
765 If set, whenever a duplicate barcode is detected, it is removed and the attempt
766 to add the record is retried, thereby giving the record a blank barcode. This
767 is useful when something has set barcodes to be a biblio ID, or similar
768 (usually other software.)
769
770 =item B<-framework>
771
772 This is the code for the framework that the requested records will have attached
773 to them when they are created. If not specified, then the default framework
774 will be used.
775
776 =item B<-custom>=I<MODULE>
777
778 This parameter allows you to use a local module with a customize subroutine
779 that is called for each MARC record.
780 If no filename is passed, LocalChanges.pm is assumed to be in the
781 migration_tools subdirectory. You may pass an absolute file name or a file name
782 from the migration_tools directory.
783
784 =back
785
786 =cut
787