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