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