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