48e2e9d99706e10982b2f88a8e192290c59639f8
[srvgit] / 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 use Encode;
20
21 use Koha::Script;
22 use C4::Context;
23 use C4::Biblio;
24 use C4::Koha;
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::Logger;
37 use Koha::Biblios;
38 use Koha::SearchEngine;
39 use Koha::SearchEngine::Search;
40
41 use open qw( :std :encoding(UTF-8) );
42 binmode( STDOUT, ":encoding(UTF-8)" );
43 my ( $input_marc_file, $number, $offset) = ('',0,0);
44 my ($version, $delete, $test_parameter, $skip_marc8_conversion, $char_encoding, $verbose, $commit, $fk_off,$format,$biblios,$authorities,$keepids,$match, $isbn_check, $logfile);
45 my ( $insert, $filters, $update, $all, $yamlfile, $authtypes, $append );
46 my $cleanisbn = 1;
47 my ($sourcetag,$sourcesubfield,$idmapfl, $dedup_barcode);
48 my $framework = '';
49 my $localcust;
50 my $marc_mod_template = '';
51 my $marc_mod_template_id = -1;
52
53 $|=1;
54
55 GetOptions(
56     'commit:f'    => \$commit,
57     'file:s'    => \$input_marc_file,
58     'n:f' => \$number,
59     'o|offset:f' => \$offset,
60     'h' => \$version,
61     'd' => \$delete,
62     't|test' => \$test_parameter,
63     's' => \$skip_marc8_conversion,
64     'c:s' => \$char_encoding,
65     'v:+' => \$verbose,
66     'fk' => \$fk_off,
67     'm:s' => \$format,
68     'l:s' => \$logfile,
69     'append' => \$append,
70     'k|keepids:s' => \$keepids,
71     'b|biblios' => \$biblios,
72     'a|authorities' => \$authorities,
73     'authtypes:s' => \$authtypes,
74     'filter=s@'     => \$filters,
75     'insert'        => \$insert,
76     'update'        => \$update,
77     'all'           => \$all,
78     'match=s@'    => \$match,
79     'i|isbn' => \$isbn_check,
80     'x:s' => \$sourcetag,
81     'y:s' => \$sourcesubfield,
82     'idmap:s' => \$idmapfl,
83     'cleanisbn!'     => \$cleanisbn,
84     'yaml:s'        => \$yamlfile,
85     'dedupbarcode' => \$dedup_barcode,
86     'framework=s' => \$framework,
87     'custom:s'    => \$localcust,
88     'marcmodtemplate:s' => \$marc_mod_template,
89 );
90 $biblios ||= !$authorities;
91 $insert  ||= !$update;
92 my $writemode = ($append) ? "a" : "w";
93
94 pod2usage( -msg => "\nYou must specify either --biblios or --authorities, not both.\n", -exitval ) if $biblios && $authorities;
95
96 if ($all) {
97     $insert = 1;
98     $update = 1;
99 }
100
101 if ($version || ($input_marc_file eq '')) {
102     pod2usage( -verbose => 2 );
103     exit;
104 }
105 if( $update && !( $match || $isbn_check ) ) {
106     warn "Using -update without -match or -isbn seems to be useless.\n";
107 }
108
109 if(defined $localcust) { #local customize module
110     if(!-e $localcust) {
111         $localcust= $localcust||'LocalChanges'; #default name
112         $localcust=~ s/^.*\/([^\/]+)$/$1/; #extract file name only
113         $localcust=~ s/\.pm$//;           #remove extension
114         my $fqcust= $FindBin::Bin."/$localcust.pm"; #try migration_tools dir
115         if(-e $fqcust) {
116             $localcust= $fqcust;
117         }
118         else {
119             print "WARNING: customize module $localcust.pm not found!\n";
120             exit 1;
121         }
122     }
123     require $localcust if $localcust;
124     $localcust=\&customize if $localcust;
125 }
126
127 if($marc_mod_template ne '') {
128     my @templates = GetModificationTemplates();
129     foreach my $this_template (@templates) {
130         if($this_template->{'name'} eq $marc_mod_template) {
131             if($marc_mod_template_id < 0) {
132                 $marc_mod_template_id = $this_template->{'template_id'};
133             } else {
134                 print "WARNING: MARC modification template name " .
135                 "'$marc_mod_template' matches multiple templates. " .
136                 "Please rename these templates\n";
137                 exit 1;
138             }
139         }
140     }
141     if($marc_mod_template_id < 0) {
142         die "Can't located MARC modification template '$marc_mod_template'\n";
143     } else {
144         print "Records will be modified using MARC modification template: $marc_mod_template\n" if $verbose;
145     }
146 }
147
148 my $dbh = C4::Context->dbh;
149 my $heading_fields=get_heading_fields();
150
151 my $idmapfh;
152 if (defined $idmapfl) {
153   open($idmapfh, '>', $idmapfl) or die "cannot open $idmapfl \n";
154 }
155
156 if ((not defined $sourcesubfield) && (not defined $sourcetag)){
157   $sourcetag="910";
158   $sourcesubfield="a";
159 }
160
161
162 # Disable logging for the biblios and authorities import operation. It would unnecessarily
163 # slow the import
164 $ENV{OVERRIDE_SYSPREF_CataloguingLog} = 0;
165 $ENV{OVERRIDE_SYSPREF_AuthoritiesLog} = 0;
166
167 if ($fk_off) {
168         $dbh->do("SET FOREIGN_KEY_CHECKS = 0");
169 }
170
171
172 if ($delete) {
173         if ($biblios){
174         print "deleting biblios\n";
175         $dbh->do("DELETE FROM biblio");
176         $dbh->do("ALTER TABLE biblio AUTO_INCREMENT = 1");
177         $dbh->do("DELETE FROM biblioitems");
178         $dbh->do("ALTER TABLE biblioitems AUTO_INCREMENT = 1");
179         $dbh->do("DELETE FROM items");
180         $dbh->do("ALTER TABLE items AUTO_INCREMENT = 1");
181         }
182         else {
183         print "deleting authorities\n";
184         $dbh->do("truncate auth_header");
185         }
186     $dbh->do("truncate zebraqueue");
187 }
188
189
190
191 if ($test_parameter) {
192     print "TESTING MODE ONLY\n    DOING NOTHING\n===============\n";
193 }
194
195 my $marcFlavour = C4::Context->preference('marcflavour') || 'MARC21';
196
197 # The definition of $searcher must be before MARC::Batch->new
198 my $searcher = Koha::SearchEngine::Search->new(
199     {
200         index => (
201               $authorities
202             ? $Koha::SearchEngine::AUTHORITIES_INDEX
203             : $Koha::SearchEngine::BIBLIOS_INDEX
204         )
205     }
206 );
207
208 print "Characteristic MARC flavour: $marcFlavour\n" if $verbose;
209 my $starttime = gettimeofday;
210 my $batch;
211 my $fh = IO::File->new($input_marc_file); # don't let MARC::Batch open the file, as it applies the ':utf8' IO layer
212 if (defined $format && $format =~ /XML/i) {
213     # ugly hack follows -- MARC::File::XML, when used by MARC::Batch,
214     # appears to try to convert incoming XML records from MARC-8
215     # to UTF-8.  Setting the BinaryEncoding key turns that off
216     # TODO: see what happens to ISO-8859-1 XML files.
217     # TODO: determine if MARC::Batch can be fixed to handle
218     #       XML records properly -- it probably should be
219     #       be using a proper push or pull XML parser to
220     #       extract the records, not using regexes to look
221     #       for <record>.*</record>.
222     $MARC::File::XML::_load_args{BinaryEncoding} = 'utf-8';
223     my $recordformat= ($marcFlavour eq "MARC21"?"USMARC":uc($marcFlavour));
224 #UNIMARC Authorities have a different way to manage encoding than UNIMARC biblios.
225     $recordformat=$recordformat."AUTH" if ($authorities and $marcFlavour ne "MARC21");
226     $MARC::File::XML::_load_args{RecordFormat} = $recordformat;
227     $batch = MARC::Batch->new( 'XML', $fh );
228 } else {
229     $batch = MARC::Batch->new( 'USMARC', $fh );
230 }
231 $batch->warnings_off();
232 $batch->strict_off();
233 my $i=0;
234 my $commitnum = $commit ? $commit : 50;
235 my $yamlhash;
236
237 # Skip file offset
238 if ( $offset ) {
239     print "Skipping file offset: $offset records\n";
240     $batch->next() while ($offset--);
241 }
242
243 my ($tagid,$subfieldid);
244 if ($authorities){
245           $tagid='001';
246 }
247 else {
248    ( $tagid, $subfieldid ) =
249             GetMarcFromKohaField( "biblio.biblionumber" );
250         $tagid||="001";
251 }
252
253 # the SQL query to search on isbn
254 my $sth_isbn = $dbh->prepare("SELECT biblionumber,biblioitemnumber FROM biblioitems WHERE isbn=?");
255
256 my $loghandle;
257 if ($logfile){
258    $loghandle= IO::File->new($logfile, $writemode) ;
259    print $loghandle "id;operation;status\n";
260 }
261
262 my $logger = Koha::Logger->get;
263 my $schema = Koha::Database->schema;
264 $schema->txn_begin;
265 RECORD: while (  ) {
266     my $record;
267     # get records
268     eval { $record = $batch->next() };
269     if ( $@ ) {
270         print "Bad MARC record $i: $@ skipped\n";
271         # FIXME - because MARC::Batch->next() combines grabbing the next
272         # blob and parsing it into one operation, a correctable condition
273         # such as a MARC-8 record claiming that it's UTF-8 can't be recovered
274         # from because we don't have access to the original blob.  Note
275         # that the staging import can deal with this condition (via
276         # C4::Charset::MarcToUTF8Record) because it doesn't use MARC::Batch.
277         next;
278     }
279     # skip if we get an empty record (that is MARC valid, but will result in AddBiblio failure
280     last unless ( $record );
281     $i++;
282     if( ($verbose//1)==1 ) { #no dot for verbose==2
283         print "." . ( $i % 100==0 ? "\n$i" : '' );
284     }
285
286     # transcode the record to UTF8 if needed & applicable.
287     if ($record->encoding() eq 'MARC-8' and not $skip_marc8_conversion) {
288         # FIXME update condition
289         my ($guessed_charset, $charset_errors);
290          ($record, $guessed_charset, $charset_errors) = MarcToUTF8Record($record, $marcFlavour.(($authorities and $marcFlavour ne "MARC21")?'AUTH':''));
291         if ($guessed_charset eq 'failed') {
292             warn "ERROR: failed to perform character conversion for record $i\n";
293             next RECORD;            
294         }
295     }
296     SetUTF8Flag($record);
297     if($marc_mod_template_id > 0) {
298     print "Modifying MARC\n" if $verbose;
299     ModifyRecordWithTemplate( $marc_mod_template_id, $record );
300     }
301     &$localcust($record) if $localcust;
302     my $isbn;
303     # remove trailing - in isbn (only for biblios, of course)
304     if( $biblios ) {
305         my $tag = $marcFlavour eq 'UNIMARC' ? '010' : '020';
306         my $field = $record->field($tag);
307         $isbn = $field && $field->subfield('a');
308         if ( $isbn && $cleanisbn ) {
309             $isbn =~ s/-//g;
310             $field->update('a' => $isbn);
311         }
312     }
313     my $id;
314     # search for duplicates (based on Local-number)
315     my $originalid;
316     $originalid = GetRecordId( $record, $tagid, $subfieldid );
317     if ($match) {
318         require C4::Search;
319         my $query = build_query( $match, $record );
320         my $server = ( $authorities ? 'authorityserver' : 'biblioserver' );
321         my ( $error, $results, $totalhits ) = $searcher->simple_search_compat( $query, 0, 3, [$server] );
322         # changed to warn so able to continue with one broken record
323         if ( defined $error ) {
324             warn "unable to search the database for duplicates : $error";
325             printlog( { id => $id || $originalid || $match, op => "match", status => "ERROR" } ) if ($logfile);
326             next RECORD;
327         }
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             $logger->debug("more than one match for $query");
353         } else {
354             $logger->debug("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                 $logger->debug("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                     $logger->debug("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 Encode::decode_utf8(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         $logger->debug(Encode::decode_utf8(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