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