1 package C4::ImportBatch;
3 # Copyright (C) 2007 LibLime, 2012 C & P Bibliography Services
5 # This file is part of Koha.
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
24 use C4::Koha qw( GetNormalizedISBN );
33 use C4::Items qw( AddItemFromMarc ModItemFromMarc );
34 use C4::Charset qw( MarcToUTF8Record SetUTF8Flag StripNonXmlChars );
35 use C4::AuthoritiesMarc qw( AddAuthority GuessAuthTypeCode GetAuthorityXML ModAuthority DelAuthority GetAuthorizedHeading );
36 use C4::MarcModificationTemplates qw( ModifyRecordWithTemplate );
38 use Koha::SearchEngine;
39 use Koha::SearchEngine::Indexer;
40 use Koha::Plugins::Handler;
43 our (@ISA, @EXPORT_OK);
55 AddItemsToImportBiblio
66 GetStagedWebserviceBatches
67 GetImportBatchRangeDesc
68 GetNumberOfNonZ3950ImportBatches
71 GetItemNumbersFromImportBatch
75 GetImportBatchOverlayAction
76 SetImportBatchOverlayAction
77 GetImportBatchNoMatchAction
78 SetImportBatchNoMatchAction
79 GetImportBatchItemAction
80 SetImportBatchItemAction
83 GetImportRecordOverlayStatus
84 SetImportRecordOverlayStatus
87 SetMatchedBiblionumber
88 GetImportRecordMatches
89 SetImportRecordMatches
91 RecordsFromMARCXMLFile
92 RecordsFromISO2709File
99 C4::ImportBatch - manage batches of imported MARC records
107 =head2 GetZ3950BatchId
109 my $batchid = GetZ3950BatchId($z3950server);
111 Retrieves the ID of the import batch for the Z39.50
112 reservoir for the given target. If necessary,
113 creates the import batch.
117 sub GetZ3950BatchId {
118 my ($z3950server) = @_;
120 my $dbh = C4::Context->dbh;
121 my $sth = $dbh->prepare("SELECT import_batch_id FROM import_batches
122 WHERE batch_type = 'z3950'
124 $sth->execute($z3950server);
125 my $rowref = $sth->fetchrow_arrayref();
127 if (defined $rowref) {
130 my $batch_id = AddImportBatch( {
131 overlay_action => 'create_new',
132 import_status => 'staged',
133 batch_type => 'z3950',
134 file_name => $z3950server,
141 =head2 GetWebserviceBatchId
143 my $batchid = GetWebserviceBatchId();
145 Retrieves the ID of the import batch for webservice.
146 If necessary, creates the import batch.
150 my $WEBSERVICE_BASE_QRY = <<EOQ;
151 SELECT import_batch_id FROM import_batches
152 WHERE batch_type = 'webservice'
153 AND import_status = 'staged'
155 sub GetWebserviceBatchId {
158 my $dbh = C4::Context->dbh;
159 my $sql = $WEBSERVICE_BASE_QRY;
161 foreach my $field (qw(matcher_id overlay_action nomatch_action item_action)) {
162 if (my $val = $params->{$field}) {
163 $sql .= " AND $field = ?";
167 my $id = $dbh->selectrow_array($sql, undef, @args);
170 $params->{batch_type} = 'webservice';
171 $params->{import_status} = 'staged';
172 return AddImportBatch($params);
175 =head2 GetImportRecordMarc
177 my ($marcblob, $encoding) = GetImportRecordMarc($import_record_id);
181 sub GetImportRecordMarc {
182 my ($import_record_id) = @_;
184 my $dbh = C4::Context->dbh;
185 my ( $marc, $encoding ) = $dbh->selectrow_array(q|
186 SELECT marc, encoding
188 WHERE import_record_id = ?
189 |, undef, $import_record_id );
191 return $marc, $encoding;
194 sub EmbedItemsInImportBiblio {
195 my ( $record, $import_record_id ) = @_;
196 my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber" );
197 my $dbh = C4::Context->dbh;
198 my $import_items = $dbh->selectall_arrayref(q|
199 SELECT import_items.marcxml
201 WHERE import_record_id = ?
202 |, { Slice => {} }, $import_record_id );
204 for my $import_item ( @$import_items ) {
205 my $item_marc = MARC::Record::new_from_xml($import_item->{marcxml}, 'UTF-8');
206 push @item_fields, $item_marc->field($itemtag);
208 $record->append_fields(@item_fields);
212 =head2 AddImportBatch
214 my $batch_id = AddImportBatch($params_hash);
222 foreach (qw( matcher_id template_id branchcode
223 overlay_action nomatch_action item_action
224 import_status batch_type file_name comments record_type )) {
225 if (exists $params->{$_}) {
227 push @vals, $params->{$_};
230 my $dbh = C4::Context->dbh;
231 $dbh->do("INSERT INTO import_batches (".join( ',', @fields).")
232 VALUES (".join( ',', map '?', @fields).")",
235 return $dbh->{'mysql_insertid'};
238 =head2 GetImportBatch
240 my $row = GetImportBatch($batch_id);
242 Retrieve a hashref of an import_batches row.
249 my $dbh = C4::Context->dbh;
250 my $sth = $dbh->prepare_cached("SELECT b.*, p.name as profile FROM import_batches b LEFT JOIN import_batch_profiles p ON p.id = b.profile_id WHERE import_batch_id = ?");
251 $sth->bind_param(1, $batch_id);
253 my $result = $sth->fetchrow_hashref;
259 =head2 AddBiblioToBatch
261 my $import_record_id = AddBiblioToBatch($batch_id, $record_sequence,
262 $marc_record, $encoding, $update_counts);
266 sub AddBiblioToBatch {
267 my $batch_id = shift;
268 my $record_sequence = shift;
269 my $marc_record = shift;
270 my $encoding = shift;
271 my $update_counts = @_ ? shift : 1;
273 my $import_record_id = _create_import_record($batch_id, $record_sequence, $marc_record, 'biblio', $encoding, C4::Context->preference('marcflavour'));
274 _add_biblio_fields($import_record_id, $marc_record);
275 _update_batch_record_counts($batch_id) if $update_counts;
276 return $import_record_id;
279 =head2 AddAuthToBatch
281 my $import_record_id = AddAuthToBatch($batch_id, $record_sequence,
282 $marc_record, $encoding, $update_counts, [$marc_type]);
287 my $batch_id = shift;
288 my $record_sequence = shift;
289 my $marc_record = shift;
290 my $encoding = shift;
291 my $update_counts = @_ ? shift : 1;
292 my $marc_type = shift || C4::Context->preference('marcflavour');
294 $marc_type = 'UNIMARCAUTH' if $marc_type eq 'UNIMARC';
296 my $import_record_id = _create_import_record($batch_id, $record_sequence, $marc_record, 'auth', $encoding, $marc_type);
297 _add_auth_fields($import_record_id, $marc_record);
298 _update_batch_record_counts($batch_id) if $update_counts;
299 return $import_record_id;
302 =head2 BatchStageMarcRecords
304 ( $batch_id, $num_records, $num_items, @invalid_records ) =
305 BatchStageMarcRecords(
306 $record_type, $encoding,
307 $marc_records, $file_name,
308 $marc_modification_template, $comments,
309 $branch_code, $parse_items,
310 $leave_as_staging, $progress_interval,
316 sub BatchStageMarcRecords {
317 my $record_type = shift;
318 my $encoding = shift;
319 my $marc_records = shift;
320 my $file_name = shift;
321 my $marc_modification_template = shift;
322 my $comments = shift;
323 my $branch_code = shift;
324 my $parse_items = shift;
325 my $leave_as_staging = shift;
327 # optional callback to monitor status
329 my $progress_interval = 0;
330 my $progress_callback = undef;
332 $progress_interval = shift;
333 $progress_callback = shift;
334 $progress_interval = 0 unless $progress_interval =~ /^\d+$/ and $progress_interval > 0;
335 $progress_interval = 0 unless 'CODE' eq ref $progress_callback;
338 my $batch_id = AddImportBatch( {
339 overlay_action => 'create_new',
340 import_status => 'staging',
341 batch_type => 'batch',
342 file_name => $file_name,
343 comments => $comments,
344 record_type => $record_type,
347 SetImportBatchItemAction($batch_id, 'always_add');
349 SetImportBatchItemAction($batch_id, 'ignore');
353 my $marc_type = C4::Context->preference('marcflavour');
354 $marc_type .= 'AUTH' if ($marc_type eq 'UNIMARC' && $record_type eq 'auth');
355 my @invalid_records = ();
358 # FIXME - for now, we're dealing only with bibs
360 foreach my $marc_record (@$marc_records) {
362 if ($progress_interval and (0 == ($rec_num % $progress_interval))) {
363 &$progress_callback($rec_num);
366 ModifyRecordWithTemplate( $marc_modification_template, $marc_record ) if ( $marc_modification_template );
368 my $import_record_id;
369 if (scalar($marc_record->fields()) == 0) {
370 push @invalid_records, $marc_record;
373 # Normalize the record so it doesn't have separated diacritics
374 SetUTF8Flag($marc_record);
377 if ($record_type eq 'biblio') {
378 $import_record_id = AddBiblioToBatch($batch_id, $rec_num, $marc_record, $encoding, 0);
380 my @import_items_ids = AddItemsToImportBiblio($batch_id, $import_record_id, $marc_record, 0);
381 $num_items += scalar(@import_items_ids);
383 } elsif ($record_type eq 'auth') {
384 $import_record_id = AddAuthToBatch($batch_id, $rec_num, $marc_record, $encoding, 0, $marc_type);
388 unless ($leave_as_staging) {
389 SetImportBatchStatus($batch_id, 'staged');
391 # FIXME branch_code, number of bibs, number of items
392 _update_batch_record_counts($batch_id);
393 if ($progress_interval){
394 &$progress_callback($rec_num);
397 return ($batch_id, $num_valid, $num_items, @invalid_records);
400 =head2 AddItemsToImportBiblio
402 my @import_items_ids = AddItemsToImportBiblio($batch_id,
403 $import_record_id, $marc_record, $update_counts);
407 sub AddItemsToImportBiblio {
408 my $batch_id = shift;
409 my $import_record_id = shift;
410 my $marc_record = shift;
411 my $update_counts = @_ ? shift : 0;
413 my @import_items_ids = ();
415 my $dbh = C4::Context->dbh;
416 my ($item_tag,$item_subfield) = &GetMarcFromKohaField( "items.itemnumber" );
417 foreach my $item_field ($marc_record->field($item_tag)) {
418 my $item_marc = MARC::Record->new();
419 $item_marc->leader("00000 a "); # must set Leader/09 to 'a'
420 $item_marc->append_fields($item_field);
421 $marc_record->delete_field($item_field);
422 my $sth = $dbh->prepare_cached("INSERT INTO import_items (import_record_id, status, marcxml)
424 $sth->bind_param(1, $import_record_id);
425 $sth->bind_param(2, 'staged');
426 $sth->bind_param(3, $item_marc->as_xml("USMARC"));
428 push @import_items_ids, $dbh->{'mysql_insertid'};
432 if ($#import_items_ids > -1) {
433 _update_batch_record_counts($batch_id) if $update_counts;
435 return @import_items_ids;
438 =head2 BatchFindDuplicates
440 my $num_with_matches = BatchFindDuplicates($batch_id, $matcher,
441 $max_matches, $progress_interval, $progress_callback);
443 Goes through the records loaded in the batch and attempts to
444 find duplicates for each one. Sets the matching status
445 of each record to "no_match" or "auto_match" as appropriate.
447 The $max_matches parameter is optional; if it is not supplied,
450 The $progress_interval and $progress_callback parameters are
451 optional; if both are supplied, the sub referred to by
452 $progress_callback will be invoked every $progress_interval
453 records using the number of records processed as the
458 sub BatchFindDuplicates {
459 my $batch_id = shift;
461 my $max_matches = @_ ? shift : 10;
463 # optional callback to monitor status
465 my $progress_interval = 0;
466 my $progress_callback = undef;
468 $progress_interval = shift;
469 $progress_callback = shift;
470 $progress_interval = 0 unless $progress_interval =~ /^\d+$/ and $progress_interval > 0;
471 $progress_interval = 0 unless 'CODE' eq ref $progress_callback;
474 my $dbh = C4::Context->dbh;
476 my $sth = $dbh->prepare("SELECT import_record_id, record_type, marc
478 WHERE import_batch_id = ?");
479 $sth->execute($batch_id);
480 my $num_with_matches = 0;
482 while (my $rowref = $sth->fetchrow_hashref) {
484 if ($progress_interval and (0 == ($rec_num % $progress_interval))) {
485 &$progress_callback($rec_num);
487 my $marc_record = MARC::Record->new_from_usmarc($rowref->{'marc'});
489 if (defined $matcher) {
490 @matches = $matcher->get_matches($marc_record, $max_matches);
492 if (scalar(@matches) > 0) {
494 SetImportRecordMatches($rowref->{'import_record_id'}, @matches);
495 SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'auto_match');
497 SetImportRecordMatches($rowref->{'import_record_id'}, ());
498 SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'no_match');
502 if ($progress_interval){
503 &$progress_callback($rec_num);
507 return $num_with_matches;
510 =head2 BatchCommitRecords
512 Takes a hashref containing params for committing the batch - optional parameters 'progress_interval' and
513 'progress_callback' will define code called every X records.
515 my ($num_added, $num_updated, $num_items_added, $num_items_replaced, $num_items_errored, $num_ignored) =
517 batch_id => $batch_id,
518 framework => $framework,
519 overlay_framework => $overlay_framework,
520 progress_interval => $progress_interval,
521 progress_callback => $progress_callback,
522 skip_intermediate_commit => $skip_intermediate_commit
525 Parameter skip_intermediate_commit does what is says.
528 sub BatchCommitRecords {
530 my $batch_id = $params->{batch_id};
531 my $framework = $params->{framework};
532 my $overlay_framework = $params->{overlay_framework};
533 my $skip_intermediate_commit = $params->{skip_intermediate_commit};
534 my $progress_interval = $params->{progress_interval} // 0;
535 my $progress_callback = $params->{progress_callback};
536 $progress_interval = 0 unless $progress_interval && $progress_interval =~ /^\d+$/;
537 $progress_interval = 0 unless ref($progress_callback) eq 'CODE';
539 my $schema = Koha::Database->schema;
541 # NOTE: Moved this transaction to the front of the routine. Note that inside the while loop below
542 # transactions may be committed and started too again. The final commit is close to the end.
547 my $num_items_added = 0;
548 my $num_items_replaced = 0;
549 my $num_items_errored = 0;
551 # commit (i.e., save, all records in the batch)
552 SetImportBatchStatus($batch_id, 'importing');
553 my $overlay_action = GetImportBatchOverlayAction($batch_id);
554 my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
555 my $item_action = GetImportBatchItemAction($batch_id);
558 my $dbh = C4::Context->dbh;
559 my $sth = $dbh->prepare("SELECT import_records.import_record_id, record_type, status, overlay_status, marc, encoding
561 LEFT JOIN import_auths ON (import_records.import_record_id=import_auths.import_record_id)
562 LEFT JOIN import_biblios ON (import_records.import_record_id=import_biblios.import_record_id)
563 WHERE import_batch_id = ?");
564 $sth->execute($batch_id);
565 my $marcflavour = C4::Context->preference('marcflavour');
567 my $userenv = C4::Context->userenv;
568 my $logged_in_patron = Koha::Patrons->find( $userenv->{number} );
572 while (my $rowref = $sth->fetchrow_hashref) {
573 $record_type = $rowref->{'record_type'};
577 if ($progress_interval and (0 == ($rec_num % $progress_interval))) {
578 # report progress and commit
579 $schema->txn_commit unless $skip_intermediate_commit;
580 &$progress_callback( $rec_num );
581 $schema->txn_begin unless $skip_intermediate_commit;
583 if ($rowref->{'status'} eq 'error' or $rowref->{'status'} eq 'imported') {
589 if ($marcflavour eq 'UNIMARC' && $record_type eq 'auth') {
590 $marc_type = 'UNIMARCAUTH';
591 } elsif ($marcflavour eq 'UNIMARC') {
592 $marc_type = 'UNIMARC';
594 $marc_type = 'USMARC';
596 my $marc_record = MARC::Record->new_from_usmarc($rowref->{'marc'});
598 if ($record_type eq 'biblio') {
599 # remove any item tags - rely on _batchCommitItems
600 ($item_tag,$item_subfield) = &GetMarcFromKohaField( "items.itemnumber" );
601 foreach my $item_field ($marc_record->field($item_tag)) {
602 $marc_record->delete_field($item_field);
604 if(C4::Context->preference('autoControlNumber') eq 'biblionumber'){
605 my @control_num = $marc_record->field('001');
606 $marc_record->delete_fields(@control_num);
610 my ($record_result, $item_result, $record_match) =
611 _get_commit_action($overlay_action, $nomatch_action, $item_action,
612 $rowref->{'overlay_status'}, $rowref->{'import_record_id'}, $record_type);
616 if ($record_result eq 'create_new') {
618 if ($record_type eq 'biblio') {
619 my $biblioitemnumber;
620 ($recordid, $biblioitemnumber) = AddBiblio($marc_record, $framework, { skip_record_index => 1 });
621 push @biblio_ids, $recordid;
622 $query = "UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?"; # FIXME call SetMatchedBiblionumber instead
623 if ($item_result eq 'create_new' || $item_result eq 'replace') {
624 my ($bib_items_added, $bib_items_replaced, $bib_items_errored) = _batchCommitItems($rowref->{'import_record_id'}, $recordid, $item_result, $biblioitemnumber);
625 $num_items_added += $bib_items_added;
626 $num_items_replaced += $bib_items_replaced;
627 $num_items_errored += $bib_items_errored;
630 $recordid = AddAuthority($marc_record, undef, GuessAuthTypeCode($marc_record));
631 $query = "UPDATE import_auths SET matched_authid = ? WHERE import_record_id = ?";
633 my $sth = $dbh->prepare_cached($query);
634 $sth->execute($recordid, $rowref->{'import_record_id'});
636 SetImportRecordStatus($rowref->{'import_record_id'}, 'imported');
637 } elsif ($record_result eq 'replace') {
639 $recordid = $record_match;
641 if ($record_type eq 'biblio') {
642 my $oldbiblio = Koha::Biblios->find( $recordid );
643 $oldxml = GetXmlBiblio($recordid);
645 # remove item fields so that they don't get
646 # added again if record is reverted
647 # FIXME: GetXmlBiblio output should not contain item info any more! So the next foreach should not be needed. Does not hurt either; may remove old 952s that should not have been there anymore.
648 my $old_marc = MARC::Record->new_from_xml(StripNonXmlChars($oldxml), 'UTF-8', $rowref->{'encoding'}, $marc_type);
649 foreach my $item_field ($old_marc->field($item_tag)) {
650 $old_marc->delete_field($item_field);
652 $oldxml = $old_marc->as_xml($marc_type);
654 my $context = { source => 'batchimport' };
655 if ($logged_in_patron) {
656 $context->{categorycode} = $logged_in_patron->categorycode;
657 $context->{userid} = $logged_in_patron->userid;
663 $overlay_framework // $oldbiblio->frameworkcode,
665 overlay_context => $context,
666 skip_record_index => 1
669 push @biblio_ids, $recordid;
670 $query = "UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?"; # FIXME call SetMatchedBiblionumber instead
672 if ($item_result eq 'create_new' || $item_result eq 'replace') {
673 my ($bib_items_added, $bib_items_replaced, $bib_items_errored) = _batchCommitItems($rowref->{'import_record_id'}, $recordid, $item_result);
674 $num_items_added += $bib_items_added;
675 $num_items_replaced += $bib_items_replaced;
676 $num_items_errored += $bib_items_errored;
679 $oldxml = GetAuthorityXML($recordid);
681 ModAuthority($recordid, $marc_record, GuessAuthTypeCode($marc_record));
682 $query = "UPDATE import_auths SET matched_authid = ? WHERE import_record_id = ?";
684 my $sth = $dbh->prepare_cached("UPDATE import_records SET marcxml_old = ? WHERE import_record_id = ?");
685 $sth->execute($oldxml, $rowref->{'import_record_id'});
687 my $sth2 = $dbh->prepare_cached($query);
688 $sth2->execute($recordid, $rowref->{'import_record_id'});
690 SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'match_applied');
691 SetImportRecordStatus($rowref->{'import_record_id'}, 'imported');
692 } elsif ($record_result eq 'ignore') {
693 $recordid = $record_match;
695 if ($record_type eq 'biblio' and defined $recordid and ( $item_result eq 'create_new' || $item_result eq 'replace' ) ) {
696 my ($bib_items_added, $bib_items_replaced, $bib_items_errored) = _batchCommitItems($rowref->{'import_record_id'}, $recordid, $item_result);
697 push @biblio_ids, $recordid if $bib_items_added || $bib_items_replaced;
698 $num_items_added += $bib_items_added;
699 $num_items_replaced += $bib_items_replaced;
700 $num_items_errored += $bib_items_errored;
701 # still need to record the matched biblionumber so that the
702 # items can be reverted
703 my $sth2 = $dbh->prepare_cached("UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?"); # FIXME call SetMatchedBiblionumber instead
704 $sth2->execute($recordid, $rowref->{'import_record_id'});
705 SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'match_applied');
707 SetImportRecordStatus($rowref->{'import_record_id'}, 'ignored');
711 if ($progress_interval){
712 &$progress_callback($rec_num);
718 my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
719 $indexer->index_records( \@biblio_ids, "specialUpdate", "biblioserver" );
722 SetImportBatchStatus($batch_id, 'imported');
724 # Moved final commit to the end
727 return ($num_added, $num_updated, $num_items_added, $num_items_replaced, $num_items_errored, $num_ignored);
730 =head2 _batchCommitItems
732 ($num_items_added, $num_items_errored) =
733 _batchCommitItems($import_record_id, $biblionumber, [$action, $biblioitemnumber]);
735 Private function for batch committing item changes. We do not trigger a re-index here, that is left to the caller.
739 sub _batchCommitItems {
740 my ( $import_record_id, $biblionumber, $action, $biblioitemnumber ) = @_;
742 my $dbh = C4::Context->dbh;
744 my $num_items_added = 0;
745 my $num_items_errored = 0;
746 my $num_items_replaced = 0;
748 my $sth = $dbh->prepare( "
749 SELECT import_items_id, import_items.marcxml, encoding
751 JOIN import_records USING (import_record_id)
752 WHERE import_record_id = ?
753 ORDER BY import_items_id
755 $sth->bind_param( 1, $import_record_id );
758 while ( my $row = $sth->fetchrow_hashref() ) {
759 my $item_marc = MARC::Record->new_from_xml( StripNonXmlChars( $row->{'marcxml'} ), 'UTF-8', $row->{'encoding'} );
761 # Delete date_due subfield as to not accidentally delete item checkout due dates
762 my ( $MARCfield, $MARCsubfield ) = GetMarcFromKohaField( 'items.onloan' );
763 $item_marc->field($MARCfield)->delete_subfield( code => $MARCsubfield );
765 my $item = TransformMarcToKoha({ record => $item_marc, kohafields => ['items.barcode','items.itemnumber'] });
768 my $duplicate_barcode = exists( $item->{'barcode'} );
769 my $duplicate_itemnumber = exists( $item->{'itemnumber'} );
771 # We assume that when replacing items we do not want to move them - the onus is on the importer to
772 # ensure the correct items/records are being updated
773 my $updsth = $dbh->prepare("UPDATE import_items SET status = ?, itemnumber = ?, import_error = ? WHERE import_items_id = ?");
775 $action eq "replace" &&
776 $duplicate_itemnumber &&
777 ( $item_match = Koha::Items->find( $item->{itemnumber} ))
779 # Duplicate itemnumbers have precedence, that way we can update barcodes by overlaying
780 ModItemFromMarc( $item_marc, $item_match->biblionumber, $item->{itemnumber}, { skip_record_index => 1 } );
781 $updsth->bind_param( 1, 'imported' );
782 $updsth->bind_param( 2, $item->{itemnumber} );
783 $updsth->bind_param( 3, undef );
784 $updsth->bind_param( 4, $row->{'import_items_id'} );
787 $num_items_replaced++;
789 $action eq "replace" &&
790 $duplicate_barcode &&
791 ( $item_match = Koha::Items->find({ barcode => $item->{'barcode'} }) )
793 ModItemFromMarc( $item_marc, $item_match->biblionumber, $item_match->itemnumber, { skip_record_index => 1 } );
794 $updsth->bind_param( 1, 'imported' );
795 $updsth->bind_param( 2, $item->{itemnumber} );
796 $updsth->bind_param( 3, undef );
797 $updsth->bind_param( 4, $row->{'import_items_id'} );
800 $num_items_replaced++;
802 # We aren't replacing, but the incoming file has a barcode, we need to check if it exists
803 $duplicate_barcode &&
804 ( $item_match = Koha::Items->find({ barcode => $item->{'barcode'} }) )
806 $updsth->bind_param( 1, 'error' );
807 $updsth->bind_param( 2, undef );
808 $updsth->bind_param( 3, 'duplicate item barcode' );
809 $updsth->bind_param( 4, $row->{'import_items_id'} );
811 $num_items_errored++;
813 # Remove the itemnumber if it exists, we want to create a new item
814 my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber" );
815 $item_marc->field($itemtag)->delete_subfield( code => $itemsubfield );
817 my ( $item_biblionumber, $biblioitemnumber, $itemnumber ) = AddItemFromMarc( $item_marc, $biblionumber, { biblioitemnumber => $biblioitemnumber, skip_record_index => 1 } );
819 $updsth->bind_param( 1, 'imported' );
820 $updsth->bind_param( 2, $itemnumber );
821 $updsth->bind_param( 3, undef );
822 $updsth->bind_param( 4, $row->{'import_items_id'} );
830 return ( $num_items_added, $num_items_replaced, $num_items_errored );
833 =head2 BatchRevertRecords
835 my ($num_deleted, $num_errors, $num_reverted, $num_items_deleted,
836 $num_ignored) = BatchRevertRecords($batch_id);
840 sub BatchRevertRecords {
841 my $batch_id = shift;
843 my $logger = Koha::Logger->get( { category => 'C4.ImportBatch' } );
845 $logger->trace("C4::ImportBatch::BatchRevertRecords( $batch_id )");
850 my $num_reverted = 0;
852 my $num_items_deleted = 0;
853 # commit (i.e., save, all records in the batch)
854 SetImportBatchStatus($batch_id, 'reverting');
855 my $overlay_action = GetImportBatchOverlayAction($batch_id);
856 my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
857 my $dbh = C4::Context->dbh;
858 my $sth = $dbh->prepare("SELECT import_records.import_record_id, record_type, status, overlay_status, marcxml_old, encoding, matched_biblionumber, matched_authid
860 LEFT JOIN import_auths ON (import_records.import_record_id=import_auths.import_record_id)
861 LEFT JOIN import_biblios ON (import_records.import_record_id=import_biblios.import_record_id)
862 WHERE import_batch_id = ?");
863 $sth->execute($batch_id);
865 my $marcflavour = C4::Context->preference('marcflavour');
866 while (my $rowref = $sth->fetchrow_hashref) {
867 $record_type = $rowref->{'record_type'};
868 if ($rowref->{'status'} eq 'error' or $rowref->{'status'} eq 'reverted') {
872 if ($marcflavour eq 'UNIMARC' && $record_type eq 'auth') {
873 $marc_type = 'UNIMARCAUTH';
874 } elsif ($marcflavour eq 'UNIMARC') {
875 $marc_type = 'UNIMARC';
877 $marc_type = 'USMARC';
880 my $record_result = _get_revert_action($overlay_action, $rowref->{'overlay_status'}, $rowref->{'status'});
882 if ($record_result eq 'delete') {
884 if ($record_type eq 'biblio') {
885 $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
886 $error = DelBiblio($rowref->{'matched_biblionumber'});
888 DelAuthority({ authid => $rowref->{'matched_authid'} });
890 if (defined $error) {
894 SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
896 } elsif ($record_result eq 'restore') {
898 my $old_record = MARC::Record->new_from_xml(StripNonXmlChars($rowref->{'marcxml_old'}), 'UTF-8', $rowref->{'encoding'}, $marc_type);
899 if ($record_type eq 'biblio') {
900 my $biblionumber = $rowref->{'matched_biblionumber'};
901 my $oldbiblio = Koha::Biblios->find( $biblionumber );
903 $logger->info("C4::ImportBatch::BatchRevertRecords: Biblio record $biblionumber does not exist, restoration of this record was skipped") unless $oldbiblio;
904 next unless $oldbiblio; # Record has since been deleted. Deleted records should stay deleted.
906 $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
907 ModBiblio($old_record, $biblionumber, $oldbiblio->frameworkcode);
909 my $authid = $rowref->{'matched_authid'};
910 ModAuthority($authid, $old_record, GuessAuthTypeCode($old_record));
912 SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
913 } elsif ($record_result eq 'ignore') {
914 if ($record_type eq 'biblio') {
915 $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
917 SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
920 if ($record_type eq 'biblio') {
921 # remove matched_biblionumber only if there is no 'imported' item left
922 $query = "UPDATE import_biblios SET matched_biblionumber = NULL WHERE import_record_id = ?"; # FIXME Remove me
923 $query = "UPDATE import_biblios SET matched_biblionumber = NULL WHERE import_record_id = ? AND NOT EXISTS (SELECT * FROM import_items WHERE import_items.import_record_id=import_biblios.import_record_id and status='imported')";
925 $query = "UPDATE import_auths SET matched_authid = NULL WHERE import_record_id = ?";
927 my $sth2 = $dbh->prepare_cached($query);
928 $sth2->execute($rowref->{'import_record_id'});
932 SetImportBatchStatus($batch_id, 'reverted');
933 return ($num_deleted, $num_errors, $num_reverted, $num_items_deleted, $num_ignored);
936 =head2 BatchRevertItems
938 my $num_items_deleted = BatchRevertItems($import_record_id, $biblionumber);
942 sub BatchRevertItems {
943 my ($import_record_id, $biblionumber) = @_;
945 my $dbh = C4::Context->dbh;
946 my $num_items_deleted = 0;
948 my $sth = $dbh->prepare_cached("SELECT import_items_id, itemnumber
950 JOIN items USING (itemnumber)
951 WHERE import_record_id = ?");
952 $sth->bind_param(1, $import_record_id);
954 while (my $row = $sth->fetchrow_hashref()) {
955 my $item = Koha::Items->find($row->{itemnumber});
956 if ($item->safe_delete){
957 my $updsth = $dbh->prepare("UPDATE import_items SET status = ? WHERE import_items_id = ?");
958 $updsth->bind_param(1, 'reverted');
959 $updsth->bind_param(2, $row->{'import_items_id'});
962 $num_items_deleted++;
969 return $num_items_deleted;
974 CleanBatch($batch_id)
976 Deletes all staged records from the import batch
977 and sets the status of the batch to 'cleaned'. Note
978 that deleting a stage record does *not* affect
979 any record that has been committed to the database.
984 my $batch_id = shift;
985 return unless defined $batch_id;
987 C4::Context->dbh->do('DELETE FROM import_records WHERE import_batch_id = ?', {}, $batch_id);
988 SetImportBatchStatus($batch_id, 'cleaned');
993 DeleteBatch($batch_id)
995 Deletes the record from the database. This can only be done
996 once the batch has been cleaned.
1001 my $batch_id = shift;
1002 return unless defined $batch_id;
1004 my $dbh = C4::Context->dbh;
1005 my $sth = $dbh->prepare('DELETE FROM import_batches WHERE import_batch_id = ?');
1006 $sth->execute( $batch_id );
1009 =head2 GetAllImportBatches
1011 my $results = GetAllImportBatches();
1013 Returns a references to an array of hash references corresponding
1014 to all import_batches rows (of batch_type 'batch'), sorted in
1015 ascending order by import_batch_id.
1019 sub GetAllImportBatches {
1020 my $dbh = C4::Context->dbh;
1021 my $sth = $dbh->prepare_cached("SELECT * FROM import_batches
1022 WHERE batch_type IN ('batch', 'webservice')
1023 ORDER BY import_batch_id ASC");
1027 while (my $row = $sth->fetchrow_hashref) {
1028 push @$results, $row;
1034 =head2 GetStagedWebserviceBatches
1036 my $batch_ids = GetStagedWebserviceBatches();
1038 Returns a references to an array of batch id's
1039 of batch_type 'webservice' that are not imported
1043 my $PENDING_WEBSERVICE_BATCHES_QRY = <<EOQ;
1044 SELECT import_batch_id FROM import_batches
1045 WHERE batch_type = 'webservice'
1046 AND import_status = 'staged'
1048 sub GetStagedWebserviceBatches {
1049 my $dbh = C4::Context->dbh;
1050 return $dbh->selectcol_arrayref($PENDING_WEBSERVICE_BATCHES_QRY);
1053 =head2 GetImportBatchRangeDesc
1055 my $results = GetImportBatchRangeDesc($offset, $results_per_group);
1057 Returns a reference to an array of hash references corresponding to
1058 import_batches rows (sorted in descending order by import_batch_id)
1059 start at the given offset.
1063 sub GetImportBatchRangeDesc {
1064 my ($offset, $results_per_group) = @_;
1066 my $dbh = C4::Context->dbh;
1067 my $query = "SELECT b.*, p.name as profile FROM import_batches b
1068 LEFT JOIN import_batch_profiles p
1069 ON b.profile_id = p.id
1070 WHERE b.batch_type IN ('batch', 'webservice')
1071 ORDER BY b.import_batch_id DESC";
1073 if ($results_per_group){
1074 $query .= " LIMIT ?";
1075 push(@params, $results_per_group);
1078 $query .= " OFFSET ?";
1079 push(@params, $offset);
1081 my $sth = $dbh->prepare_cached($query);
1082 $sth->execute(@params);
1083 my $results = $sth->fetchall_arrayref({});
1088 =head2 GetItemNumbersFromImportBatch
1090 my @itemsnos = GetItemNumbersFromImportBatch($batch_id);
1094 sub GetItemNumbersFromImportBatch {
1095 my ($batch_id) = @_;
1096 my $dbh = C4::Context->dbh;
1098 SELECT itemnumber FROM import_items
1099 INNER JOIN items USING (itemnumber)
1100 INNER JOIN import_records USING (import_record_id)
1101 WHERE import_batch_id = ?|;
1102 my $sth = $dbh->prepare( $sql );
1103 $sth->execute($batch_id);
1105 while ( my ($itm) = $sth->fetchrow_array ) {
1111 =head2 GetNumberOfImportBatches
1113 my $count = GetNumberOfImportBatches();
1117 sub GetNumberOfNonZ3950ImportBatches {
1118 my $dbh = C4::Context->dbh;
1119 my $sth = $dbh->prepare("SELECT COUNT(*) FROM import_batches WHERE batch_type != 'z3950'");
1121 my ($count) = $sth->fetchrow_array();
1126 =head2 GetImportBiblios
1128 my $results = GetImportBiblios($importid);
1132 sub GetImportBiblios {
1133 my ($import_record_id) = @_;
1135 my $dbh = C4::Context->dbh;
1136 my $query = "SELECT * FROM import_biblios WHERE import_record_id = ?";
1137 return $dbh->selectall_arrayref(
1145 =head2 GetImportRecordsRange
1147 my $results = GetImportRecordsRange($batch_id, $offset, $results_per_group);
1149 Returns a reference to an array of hash references corresponding to
1150 import_biblios/import_auths/import_records rows for a given batch
1151 starting at the given offset.
1155 sub GetImportRecordsRange {
1156 my ( $batch_id, $offset, $results_per_group, $status, $parameters ) = @_;
1158 my $dbh = C4::Context->dbh;
1160 my $order_by = $parameters->{order_by} || 'import_record_id';
1161 ( $order_by ) = grep( { $_ eq $order_by } qw( import_record_id title status overlay_status ) ) ? $order_by : 'import_record_id';
1163 my $order_by_direction =
1164 uc( $parameters->{order_by_direction} // 'ASC' ) eq 'DESC' ? 'DESC' : 'ASC';
1166 $order_by .= " $order_by_direction, authorized_heading" if $order_by eq 'title';
1168 my $query = "SELECT title, author, isbn, issn, authorized_heading, import_records.import_record_id,
1169 record_sequence, status, overlay_status,
1170 matched_biblionumber, matched_authid, record_type
1172 LEFT JOIN import_auths ON (import_records.import_record_id=import_auths.import_record_id)
1173 LEFT JOIN import_biblios ON (import_records.import_record_id=import_biblios.import_record_id)
1174 WHERE import_batch_id = ?";
1176 push(@params, $batch_id);
1178 $query .= " AND status=?";
1179 push(@params,$status);
1182 $query.=" ORDER BY $order_by $order_by_direction";
1184 if($results_per_group){
1185 $query .= " LIMIT ?";
1186 push(@params, $results_per_group);
1189 $query .= " OFFSET ?";
1190 push(@params, $offset);
1192 my $sth = $dbh->prepare_cached($query);
1193 $sth->execute(@params);
1194 my $results = $sth->fetchall_arrayref({});
1200 =head2 GetBestRecordMatch
1202 my $record_id = GetBestRecordMatch($import_record_id);
1206 sub GetBestRecordMatch {
1207 my ($import_record_id) = @_;
1209 my $dbh = C4::Context->dbh;
1210 my $sth = $dbh->prepare("SELECT candidate_match_id
1211 FROM import_record_matches
1212 JOIN import_records ON ( import_record_matches.import_record_id = import_records.import_record_id )
1213 LEFT JOIN biblio ON ( candidate_match_id = biblio.biblionumber )
1214 LEFT JOIN auth_header ON ( candidate_match_id = auth_header.authid )
1215 WHERE import_record_matches.import_record_id = ? AND
1216 ( (import_records.record_type = 'biblio' AND biblio.biblionumber IS NOT NULL) OR
1217 (import_records.record_type = 'auth' AND auth_header.authid IS NOT NULL) )
1219 ORDER BY score DESC, candidate_match_id DESC");
1220 $sth->execute($import_record_id);
1221 my ($record_id) = $sth->fetchrow_array();
1226 =head2 GetImportBatchStatus
1228 my $status = GetImportBatchStatus($batch_id);
1232 sub GetImportBatchStatus {
1233 my ($batch_id) = @_;
1235 my $dbh = C4::Context->dbh;
1236 my $sth = $dbh->prepare("SELECT import_status FROM import_batches WHERE import_batch_id = ?");
1237 $sth->execute($batch_id);
1238 my ($status) = $sth->fetchrow_array();
1244 =head2 SetImportBatchStatus
1246 SetImportBatchStatus($batch_id, $new_status);
1250 sub SetImportBatchStatus {
1251 my ($batch_id, $new_status) = @_;
1253 my $dbh = C4::Context->dbh;
1254 my $sth = $dbh->prepare("UPDATE import_batches SET import_status = ? WHERE import_batch_id = ?");
1255 $sth->execute($new_status, $batch_id);
1260 =head2 SetMatchedBiblionumber
1262 SetMatchedBiblionumber($import_record_id, $biblionumber);
1266 sub SetMatchedBiblionumber {
1267 my ($import_record_id, $biblionumber) = @_;
1269 my $dbh = C4::Context->dbh;
1271 q|UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?|,
1272 undef, $biblionumber, $import_record_id
1276 =head2 GetImportBatchOverlayAction
1278 my $overlay_action = GetImportBatchOverlayAction($batch_id);
1282 sub GetImportBatchOverlayAction {
1283 my ($batch_id) = @_;
1285 my $dbh = C4::Context->dbh;
1286 my $sth = $dbh->prepare("SELECT overlay_action FROM import_batches WHERE import_batch_id = ?");
1287 $sth->execute($batch_id);
1288 my ($overlay_action) = $sth->fetchrow_array();
1290 return $overlay_action;
1295 =head2 SetImportBatchOverlayAction
1297 SetImportBatchOverlayAction($batch_id, $new_overlay_action);
1301 sub SetImportBatchOverlayAction {
1302 my ($batch_id, $new_overlay_action) = @_;
1304 my $dbh = C4::Context->dbh;
1305 my $sth = $dbh->prepare("UPDATE import_batches SET overlay_action = ? WHERE import_batch_id = ?");
1306 $sth->execute($new_overlay_action, $batch_id);
1311 =head2 GetImportBatchNoMatchAction
1313 my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
1317 sub GetImportBatchNoMatchAction {
1318 my ($batch_id) = @_;
1320 my $dbh = C4::Context->dbh;
1321 my $sth = $dbh->prepare("SELECT nomatch_action FROM import_batches WHERE import_batch_id = ?");
1322 $sth->execute($batch_id);
1323 my ($nomatch_action) = $sth->fetchrow_array();
1325 return $nomatch_action;
1330 =head2 SetImportBatchNoMatchAction
1332 SetImportBatchNoMatchAction($batch_id, $new_nomatch_action);
1336 sub SetImportBatchNoMatchAction {
1337 my ($batch_id, $new_nomatch_action) = @_;
1339 my $dbh = C4::Context->dbh;
1340 my $sth = $dbh->prepare("UPDATE import_batches SET nomatch_action = ? WHERE import_batch_id = ?");
1341 $sth->execute($new_nomatch_action, $batch_id);
1346 =head2 GetImportBatchItemAction
1348 my $item_action = GetImportBatchItemAction($batch_id);
1352 sub GetImportBatchItemAction {
1353 my ($batch_id) = @_;
1355 my $dbh = C4::Context->dbh;
1356 my $sth = $dbh->prepare("SELECT item_action FROM import_batches WHERE import_batch_id = ?");
1357 $sth->execute($batch_id);
1358 my ($item_action) = $sth->fetchrow_array();
1360 return $item_action;
1365 =head2 SetImportBatchItemAction
1367 SetImportBatchItemAction($batch_id, $new_item_action);
1371 sub SetImportBatchItemAction {
1372 my ($batch_id, $new_item_action) = @_;
1374 my $dbh = C4::Context->dbh;
1375 my $sth = $dbh->prepare("UPDATE import_batches SET item_action = ? WHERE import_batch_id = ?");
1376 $sth->execute($new_item_action, $batch_id);
1381 =head2 GetImportBatchMatcher
1383 my $matcher_id = GetImportBatchMatcher($batch_id);
1387 sub GetImportBatchMatcher {
1388 my ($batch_id) = @_;
1390 my $dbh = C4::Context->dbh;
1391 my $sth = $dbh->prepare("SELECT matcher_id FROM import_batches WHERE import_batch_id = ?");
1392 $sth->execute($batch_id);
1393 my ($matcher_id) = $sth->fetchrow_array();
1400 =head2 SetImportBatchMatcher
1402 SetImportBatchMatcher($batch_id, $new_matcher_id);
1406 sub SetImportBatchMatcher {
1407 my ($batch_id, $new_matcher_id) = @_;
1409 my $dbh = C4::Context->dbh;
1410 my $sth = $dbh->prepare("UPDATE import_batches SET matcher_id = ? WHERE import_batch_id = ?");
1411 $sth->execute($new_matcher_id, $batch_id);
1416 =head2 GetImportRecordOverlayStatus
1418 my $overlay_status = GetImportRecordOverlayStatus($import_record_id);
1422 sub GetImportRecordOverlayStatus {
1423 my ($import_record_id) = @_;
1425 my $dbh = C4::Context->dbh;
1426 my $sth = $dbh->prepare("SELECT overlay_status FROM import_records WHERE import_record_id = ?");
1427 $sth->execute($import_record_id);
1428 my ($overlay_status) = $sth->fetchrow_array();
1430 return $overlay_status;
1435 =head2 SetImportRecordOverlayStatus
1437 SetImportRecordOverlayStatus($import_record_id, $new_overlay_status);
1441 sub SetImportRecordOverlayStatus {
1442 my ($import_record_id, $new_overlay_status) = @_;
1444 my $dbh = C4::Context->dbh;
1445 my $sth = $dbh->prepare("UPDATE import_records SET overlay_status = ? WHERE import_record_id = ?");
1446 $sth->execute($new_overlay_status, $import_record_id);
1451 =head2 GetImportRecordStatus
1453 my $status = GetImportRecordStatus($import_record_id);
1457 sub GetImportRecordStatus {
1458 my ($import_record_id) = @_;
1460 my $dbh = C4::Context->dbh;
1461 my $sth = $dbh->prepare("SELECT status FROM import_records WHERE import_record_id = ?");
1462 $sth->execute($import_record_id);
1463 my ($status) = $sth->fetchrow_array();
1470 =head2 SetImportRecordStatus
1472 SetImportRecordStatus($import_record_id, $new_status);
1476 sub SetImportRecordStatus {
1477 my ($import_record_id, $new_status) = @_;
1479 my $dbh = C4::Context->dbh;
1480 my $sth = $dbh->prepare("UPDATE import_records SET status = ? WHERE import_record_id = ?");
1481 $sth->execute($new_status, $import_record_id);
1486 =head2 GetImportRecordMatches
1488 my $results = GetImportRecordMatches($import_record_id, $best_only);
1492 sub GetImportRecordMatches {
1493 my $import_record_id = shift;
1494 my $best_only = @_ ? shift : 0;
1496 my $dbh = C4::Context->dbh;
1497 # FIXME currently biblio only
1498 my $sth = $dbh->prepare_cached("SELECT title, author, biblionumber,
1499 candidate_match_id, score, record_type,
1502 JOIN import_record_matches USING (import_record_id)
1503 LEFT JOIN biblio ON (biblionumber = candidate_match_id)
1504 WHERE import_record_id = ?
1505 ORDER BY score DESC, biblionumber DESC");
1506 $sth->bind_param(1, $import_record_id);
1509 while (my $row = $sth->fetchrow_hashref) {
1510 if ($row->{'record_type'} eq 'auth') {
1511 $row->{'authorized_heading'} = GetAuthorizedHeading( { authid => $row->{'candidate_match_id'} } );
1513 next if ($row->{'record_type'} eq 'biblio' && not $row->{'biblionumber'});
1514 push @$results, $row;
1523 =head2 SetImportRecordMatches
1525 SetImportRecordMatches($import_record_id, @matches);
1529 sub SetImportRecordMatches {
1530 my $import_record_id = shift;
1533 my $dbh = C4::Context->dbh;
1534 my $delsth = $dbh->prepare("DELETE FROM import_record_matches WHERE import_record_id = ?");
1535 $delsth->execute($import_record_id);
1538 my $sth = $dbh->prepare("INSERT INTO import_record_matches (import_record_id, candidate_match_id, score, chosen)
1539 VALUES (?, ?, ?, ?)");
1540 my $chosen = 1; #The first match is defaulted to be chosen
1541 foreach my $match (@matches) {
1542 $sth->execute($import_record_id, $match->{'record_id'}, $match->{'score'}, $chosen);
1543 $chosen = 0; #After the first we do not default to other matches
1547 =head2 RecordsFromISO2709File
1549 my ($errors, $records) = C4::ImportBatch::RecordsFromISO2709File($input_file, $record_type, $encoding);
1551 Reads ISO2709 binary porridge from the given file and creates MARC::Record-objects out of it.
1553 @PARAM1, String, absolute path to the ISO2709 file.
1554 @PARAM2, String, see stage_file.pl
1555 @PARAM3, String, should be utf8
1557 Returns two array refs.
1561 sub RecordsFromISO2709File {
1562 my ($input_file, $record_type, $encoding) = @_;
1565 my $marc_type = C4::Context->preference('marcflavour');
1566 $marc_type .= 'AUTH' if ($marc_type eq 'UNIMARC' && $record_type eq 'auth');
1568 open my $fh, '<', $input_file or die "$0: cannot open input file $input_file: $!\n";
1574 next unless $_; # skip if record has only whitespace, as might occur
1575 # if file includes newlines between each MARC record
1576 my ($marc_record, $charset_guessed, $char_errors) = MarcToUTF8Record($_, $marc_type, $encoding);
1577 push @marc_records, $marc_record;
1578 if ($charset_guessed ne $encoding) {
1580 "Unexpected charset $charset_guessed, expecting $encoding";
1584 return ( \@errors, \@marc_records );
1587 =head2 RecordsFromMARCXMLFile
1589 my ($errors, $records) = C4::ImportBatch::RecordsFromMARCXMLFile($input_file, $encoding);
1591 Creates MARC::Record-objects out of the given MARCXML-file.
1593 @PARAM1, String, absolute path to the ISO2709 file.
1594 @PARAM2, String, should be utf8
1596 Returns two array refs.
1600 sub RecordsFromMARCXMLFile {
1601 my ( $filename, $encoding ) = @_;
1602 my $batch = MARC::File::XML->in( $filename );
1603 my ( @marcRecords, @errors, $record );
1605 eval { $record = $batch->next( $encoding ); };
1609 push @marcRecords, $record if $record;
1611 return (\@errors, \@marcRecords);
1614 =head2 RecordsFromMarcPlugin
1616 Converts text of input_file into array of MARC records with to_marc plugin
1620 sub RecordsFromMarcPlugin {
1621 my ($input_file, $plugin_class, $encoding) = @_;
1622 my ( $text, @return );
1623 return \@return if !$input_file || !$plugin_class;
1626 open my $fh, '<', $input_file or die "$0: cannot open input file $input_file: $!\n";
1636 # Convert to large MARC blob with plugin
1637 $text = Koha::Plugins::Handler->run({
1638 class => $plugin_class,
1639 method => 'to_marc',
1640 params => { data => $text },
1643 # Convert to array of MARC records
1645 my $marc_type = C4::Context->preference('marcflavour');
1646 foreach my $blob ( split(/\x1D/, $text) ) {
1647 next if $blob =~ /^\s*$/;
1648 my ($marcrecord) = MarcToUTF8Record($blob, $marc_type, $encoding);
1649 push @return, $marcrecord;
1655 # internal functions
1657 sub _create_import_record {
1658 my ($batch_id, $record_sequence, $marc_record, $record_type, $encoding, $marc_type) = @_;
1660 my $dbh = C4::Context->dbh;
1661 my $sth = $dbh->prepare("INSERT INTO import_records (import_batch_id, record_sequence, marc, marcxml, marcxml_old,
1662 record_type, encoding)
1663 VALUES (?, ?, ?, ?, ?, ?, ?)");
1664 $sth->execute($batch_id, $record_sequence, $marc_record->as_usmarc(), $marc_record->as_xml($marc_type), '',
1665 $record_type, $encoding);
1666 my $import_record_id = $dbh->{'mysql_insertid'};
1668 return $import_record_id;
1671 sub _add_auth_fields {
1672 my ($import_record_id, $marc_record) = @_;
1675 if ($marc_record->field('001')) {
1676 $controlnumber = $marc_record->field('001')->data();
1678 my $authorized_heading = GetAuthorizedHeading({ record => $marc_record });
1679 my $dbh = C4::Context->dbh;
1680 my $sth = $dbh->prepare("INSERT INTO import_auths (import_record_id, control_number, authorized_heading) VALUES (?, ?, ?)");
1681 $sth->execute($import_record_id, $controlnumber, $authorized_heading);
1685 sub _add_biblio_fields {
1686 my ($import_record_id, $marc_record) = @_;
1688 my ($title, $author, $isbn, $issn) = _parse_biblio_fields($marc_record);
1689 my $dbh = C4::Context->dbh;
1690 # FIXME no controlnumber, originalsource
1691 $isbn = C4::Koha::GetNormalizedISBN($isbn);
1692 my $sth = $dbh->prepare("INSERT INTO import_biblios (import_record_id, title, author, isbn, issn) VALUES (?, ?, ?, ?, ?)");
1693 $sth->execute($import_record_id, $title, $author, $isbn, $issn) or die $sth->errstr;
1698 sub _update_biblio_fields {
1699 my ($import_record_id, $marc_record) = @_;
1701 my ($title, $author, $isbn, $issn) = _parse_biblio_fields($marc_record);
1702 my $dbh = C4::Context->dbh;
1703 # FIXME no controlnumber, originalsource
1704 # FIXME 2 - should regularize normalization of ISBN wherever it is done
1708 my $sth = $dbh->prepare("UPDATE import_biblios SET title = ?, author = ?, isbn = ?, issn = ?
1709 WHERE import_record_id = ?");
1710 $sth->execute($title, $author, $isbn, $issn, $import_record_id);
1714 sub _parse_biblio_fields {
1715 my ($marc_record) = @_;
1717 my $dbh = C4::Context->dbh;
1718 my $bibliofields = TransformMarcToKoha({ record => $marc_record, kohafields => ['biblio.title','biblio.author','biblioitems.isbn','biblioitems.issn'] });
1719 return ($bibliofields->{'title'}, $bibliofields->{'author'}, $bibliofields->{'isbn'}, $bibliofields->{'issn'});
1723 sub _update_batch_record_counts {
1724 my ($batch_id) = @_;
1726 my $dbh = C4::Context->dbh;
1727 my ( $num_records ) = $dbh->selectrow_array(q|
1730 WHERE import_batch_id = ?
1731 |, undef, $batch_id );
1732 my ( $num_items ) = $dbh->selectrow_array(q|
1735 JOIN import_items USING (import_record_id)
1736 WHERE import_batch_id = ? AND record_type = 'biblio'
1737 |, undef, $batch_id );
1739 "UPDATE import_batches SET num_records=?, num_items=? WHERE import_batch_id=?",
1747 sub _get_commit_action {
1748 my ($overlay_action, $nomatch_action, $item_action, $overlay_status, $import_record_id, $record_type) = @_;
1750 if ($record_type eq 'biblio') {
1751 my ($bib_result, $bib_match, $item_result);
1753 $bib_match = GetBestRecordMatch($import_record_id);
1754 if ($overlay_status ne 'no_match' && defined($bib_match)) {
1756 $bib_result = $overlay_action;
1758 if($item_action eq 'always_add' or $item_action eq 'add_only_for_matches'){
1759 $item_result = 'create_new';
1760 } elsif($item_action eq 'replace'){
1761 $item_result = 'replace';
1763 $item_result = 'ignore';
1767 $bib_result = $nomatch_action;
1768 $item_result = ($item_action eq 'always_add' or $item_action eq 'add_only_for_new') ? 'create_new' : 'ignore';
1770 return ($bib_result, $item_result, $bib_match);
1771 } else { # must be auths
1772 my ($auth_result, $auth_match);
1774 $auth_match = GetBestRecordMatch($import_record_id);
1775 if ($overlay_status ne 'no_match' && defined($auth_match)) {
1776 $auth_result = $overlay_action;
1778 $auth_result = $nomatch_action;
1781 return ($auth_result, undef, $auth_match);
1786 sub _get_revert_action {
1787 my ($overlay_action, $overlay_status, $status) = @_;
1791 if ($status eq 'ignored') {
1792 $bib_result = 'ignore';
1794 if ($overlay_action eq 'create_new') {
1795 $bib_result = 'delete';
1797 $bib_result = ($overlay_status eq 'match_applied') ? 'restore' : 'delete';
1808 Koha Development Team <http://koha-community.org/>
1810 Galen Charlton <galen.charlton@liblime.com>