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 under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License along
17 # with Koha; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
28 use C4::AuthoritiesMarc;
30 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
33 # set the version for version checking
34 $VERSION = 3.07.00.049;
41 GetImportRecordMarcXML
46 AddItemsToImportBiblio
57 GetStagedWebserviceBatches
58 GetImportBatchRangeDesc
59 GetNumberOfNonZ3950ImportBatches
61 GetItemNumbersFromImportBatch
65 GetImportBatchOverlayAction
66 SetImportBatchOverlayAction
67 GetImportBatchNoMatchAction
68 SetImportBatchNoMatchAction
69 GetImportBatchItemAction
70 SetImportBatchItemAction
73 GetImportRecordOverlayStatus
74 SetImportRecordOverlayStatus
77 GetImportRecordMatches
78 SetImportRecordMatches
84 C4::ImportBatch - manage batches of imported MARC records
92 =head2 GetZ3950BatchId
94 my $batchid = GetZ3950BatchId($z3950server);
96 Retrieves the ID of the import batch for the Z39.50
97 reservoir for the given target. If necessary,
98 creates the import batch.
102 sub GetZ3950BatchId {
103 my ($z3950server) = @_;
105 my $dbh = C4::Context->dbh;
106 my $sth = $dbh->prepare("SELECT import_batch_id FROM import_batches
107 WHERE batch_type = 'z3950'
109 $sth->execute($z3950server);
110 my $rowref = $sth->fetchrow_arrayref();
112 if (defined $rowref) {
115 my $batch_id = AddImportBatch( {
116 overlay_action => 'create_new',
117 import_status => 'staged',
118 batch_type => 'z3950',
119 file_name => $z3950server,
126 =head2 GetWebserviceBatchId
128 my $batchid = GetWebserviceBatchId();
130 Retrieves the ID of the import batch for webservice.
131 If necessary, creates the import batch.
135 my $WEBSERVICE_BASE_QRY = <<EOQ;
136 SELECT import_batch_id FROM import_batches
137 WHERE batch_type = 'webservice'
138 AND import_status = 'staged'
140 sub GetWebserviceBatchId {
143 my $dbh = C4::Context->dbh;
144 my $sql = $WEBSERVICE_BASE_QRY;
146 foreach my $field (qw(matcher_id overlay_action nomatch_action item_action)) {
147 if (my $val = $params->{$field}) {
148 $sql .= " AND $field = ?";
152 my $id = $dbh->selectrow_array($sql, undef, @args);
155 $params->{batch_type} = 'webservice';
156 $params->{import_status} = 'staged';
157 return AddImportBatch($params);
160 =head2 GetImportRecordMarc
162 my ($marcblob, $encoding) = GetImportRecordMarc($import_record_id);
166 sub GetImportRecordMarc {
167 my ($import_record_id) = @_;
169 my $dbh = C4::Context->dbh;
170 my $sth = $dbh->prepare("SELECT marc, encoding FROM import_records WHERE import_record_id = ?");
171 $sth->execute($import_record_id);
172 my ($marc, $encoding) = $sth->fetchrow();
174 return $marc, $encoding;
178 =head2 GetImportRecordMarcXML
180 my $marcxml = GetImportRecordMarcXML($import_record_id);
184 sub GetImportRecordMarcXML {
185 my ($import_record_id) = @_;
187 my $dbh = C4::Context->dbh;
188 my $sth = $dbh->prepare("SELECT marcxml FROM import_records WHERE import_record_id = ?");
189 $sth->execute($import_record_id);
190 my ($marcxml) = $sth->fetchrow();
196 =head2 AddImportBatch
198 my $batch_id = AddImportBatch($params_hash);
206 foreach (qw( matcher_id template_id branchcode
207 overlay_action nomatch_action item_action
208 import_status batch_type file_name comments )) {
209 if (exists $params->{$_}) {
211 push @vals, $params->{$_};
214 my $dbh = C4::Context->dbh;
215 $dbh->do("INSERT INTO import_batches (".join( ',', @fields).")
216 VALUES (".join( ',', map '?', @fields).")",
219 return $dbh->{'mysql_insertid'};
222 =head2 GetImportBatch
224 my $row = GetImportBatch($batch_id);
226 Retrieve a hashref of an import_batches row.
233 my $dbh = C4::Context->dbh;
234 my $sth = $dbh->prepare_cached("SELECT * FROM import_batches WHERE import_batch_id = ?");
235 $sth->bind_param(1, $batch_id);
237 my $result = $sth->fetchrow_hashref;
243 =head2 AddBiblioToBatch
245 my $import_record_id = AddBiblioToBatch($batch_id, $record_sequence,
246 $marc_record, $encoding, $z3950random, $update_counts);
250 sub AddBiblioToBatch {
251 my $batch_id = shift;
252 my $record_sequence = shift;
253 my $marc_record = shift;
254 my $encoding = shift;
255 my $z3950random = shift;
256 my $update_counts = @_ ? shift : 1;
258 my $import_record_id = _create_import_record($batch_id, $record_sequence, $marc_record, 'biblio', $encoding, $z3950random);
259 _add_biblio_fields($import_record_id, $marc_record);
260 _update_batch_record_counts($batch_id) if $update_counts;
261 return $import_record_id;
264 =head2 ModBiblioInBatch
266 ModBiblioInBatch($import_record_id, $marc_record);
270 sub ModBiblioInBatch {
271 my ($import_record_id, $marc_record) = @_;
273 _update_import_record_marc($import_record_id, $marc_record);
274 _update_biblio_fields($import_record_id, $marc_record);
278 =head2 AddAuthToBatch
280 my $import_record_id = AddAuthToBatch($batch_id, $record_sequence,
281 $marc_record, $encoding, $z3950random, $update_counts);
286 my $batch_id = shift;
287 my $record_sequence = shift;
288 my $marc_record = shift;
289 my $encoding = shift;
290 my $z3950random = shift;
291 my $update_counts = @_ ? shift : 1;
293 my $import_record_id = _create_import_record($batch_id, $record_sequence, $marc_record, 'auth', $encoding, $z3950random);
294 _add_auth_fields($import_record_id, $marc_record);
295 _update_batch_record_counts($batch_id) if $update_counts;
296 return $import_record_id;
299 =head2 ModAuthInBatch
301 ModAuthInBatch($import_record_id, $marc_record);
306 my ($import_record_id, $marc_record) = @_;
308 _update_import_record_marc($import_record_id, $marc_record);
312 =head2 BatchStageMarcRecords
314 ($batch_id, $num_records, $num_items, @invalid_records) =
315 BatchStageMarcRecords($record_type, $encoding, $marc_records, $file_name,
316 $comments, $branch_code, $parse_items,
318 $progress_interval, $progress_callback);
322 sub BatchStageMarcRecords {
323 my $record_type = shift;
324 my $encoding = shift;
325 my $marc_records = shift;
326 my $file_name = shift;
327 my $comments = shift;
328 my $branch_code = shift;
329 my $parse_items = shift;
330 my $leave_as_staging = shift;
332 # optional callback to monitor status
334 my $progress_interval = 0;
335 my $progress_callback = undef;
337 $progress_interval = shift;
338 $progress_callback = shift;
339 $progress_interval = 0 unless $progress_interval =~ /^\d+$/ and $progress_interval > 0;
340 $progress_interval = 0 unless 'CODE' eq ref $progress_callback;
343 my $batch_id = AddImportBatch( {
344 overlay_action => 'create_new',
345 import_status => 'staging',
346 batch_type => 'batch',
347 file_name => $file_name,
348 comments => $comments,
351 SetImportBatchItemAction($batch_id, 'always_add');
353 SetImportBatchItemAction($batch_id, 'ignore');
356 my @invalid_records = ();
359 # FIXME - for now, we're dealing only with bibs
361 foreach my $marc_blob (split(/\x1D/, $marc_records)) {
362 $marc_blob =~ s/^\s+//g;
363 $marc_blob =~ s/\s+$//g;
364 next unless $marc_blob;
366 if ($progress_interval and (0 == ($rec_num % $progress_interval))) {
367 &$progress_callback($rec_num);
369 my ($marc_record, $charset_guessed, $char_errors) =
370 MarcToUTF8Record($marc_blob, C4::Context->preference("marcflavour"), $encoding);
372 $encoding = $charset_guessed unless $encoding;
374 my $import_record_id;
375 if (scalar($marc_record->fields()) == 0) {
376 push @invalid_records, $marc_blob;
379 if ($record_type eq 'biblio') {
380 $import_record_id = AddBiblioToBatch($batch_id, $rec_num, $marc_record, $encoding, int(rand(99999)), 0);
382 my @import_items_ids = AddItemsToImportBiblio($batch_id, $import_record_id, $marc_record, 0);
383 $num_items += scalar(@import_items_ids);
385 } elsif ($record_type eq 'auth') {
386 $import_record_id = AddAuthToBatch($batch_id, $rec_num, $marc_record, $encoding, int(rand(99999)), 0);
390 unless ($leave_as_staging) {
391 SetImportBatchStatus($batch_id, 'staged');
393 # FIXME branch_code, number of bibs, number of items
394 _update_batch_record_counts($batch_id);
395 return ($batch_id, $num_valid, $num_items, @invalid_records);
398 =head2 AddItemsToImportBiblio
400 my @import_items_ids = AddItemsToImportBiblio($batch_id,
401 $import_record_id, $marc_record, $update_counts);
405 sub AddItemsToImportBiblio {
406 my $batch_id = shift;
407 my $import_record_id = shift;
408 my $marc_record = shift;
409 my $update_counts = @_ ? shift : 0;
411 my @import_items_ids = ();
413 my $dbh = C4::Context->dbh;
414 my ($item_tag,$item_subfield) = &GetMarcFromKohaField("items.itemnumber",'');
415 foreach my $item_field ($marc_record->field($item_tag)) {
416 my $item_marc = MARC::Record->new();
417 $item_marc->leader("00000 a "); # must set Leader/09 to 'a'
418 $item_marc->append_fields($item_field);
419 $marc_record->delete_field($item_field);
420 my $sth = $dbh->prepare_cached("INSERT INTO import_items (import_record_id, status, marcxml)
422 $sth->bind_param(1, $import_record_id);
423 $sth->bind_param(2, 'staged');
424 $sth->bind_param(3, $item_marc->as_xml());
426 push @import_items_ids, $dbh->{'mysql_insertid'};
430 if ($#import_items_ids > -1) {
431 _update_batch_record_counts($batch_id) if $update_counts;
432 _update_import_record_marc($import_record_id, $marc_record);
434 return @import_items_ids;
437 =head2 BatchFindDuplicates
439 my $num_with_matches = BatchFindDuplicates($batch_id, $matcher,
440 $max_matches, $progress_interval, $progress_callback);
442 Goes through the records loaded in the batch and attempts to
443 find duplicates for each one. Sets the matching status
444 of each record to "no_match" or "auto_match" as appropriate.
446 The $max_matches parameter is optional; if it is not supplied,
449 The $progress_interval and $progress_callback parameters are
450 optional; if both are supplied, the sub referred to by
451 $progress_callback will be invoked every $progress_interval
452 records using the number of records processed as the
457 sub BatchFindDuplicates {
458 my $batch_id = shift;
460 my $max_matches = @_ ? shift : 10;
462 # optional callback to monitor status
464 my $progress_interval = 0;
465 my $progress_callback = undef;
467 $progress_interval = shift;
468 $progress_callback = shift;
469 $progress_interval = 0 unless $progress_interval =~ /^\d+$/ and $progress_interval > 0;
470 $progress_interval = 0 unless 'CODE' eq ref $progress_callback;
473 my $dbh = C4::Context->dbh;
475 my $sth = $dbh->prepare("SELECT import_record_id, record_type, marc
477 WHERE import_batch_id = ?");
478 $sth->execute($batch_id);
479 my $num_with_matches = 0;
481 while (my $rowref = $sth->fetchrow_hashref) {
483 if ($progress_interval and (0 == ($rec_num % $progress_interval))) {
484 &$progress_callback($rec_num);
486 my $marc_record = MARC::Record->new_from_usmarc($rowref->{'marc'});
488 if (defined $matcher) {
489 @matches = $matcher->get_matches($marc_record, $max_matches);
491 if (scalar(@matches) > 0) {
493 SetImportRecordMatches($rowref->{'import_record_id'}, @matches);
494 SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'auto_match');
496 SetImportRecordMatches($rowref->{'import_record_id'}, ());
497 SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'no_match');
501 return $num_with_matches;
504 =head2 BatchCommitRecords
506 my ($num_added, $num_updated, $num_items_added, $num_items_errored, $num_ignored) =
507 BatchCommitRecords($batch_id, $framework,
508 $progress_interval, $progress_callback);
512 sub BatchCommitRecords {
513 my $batch_id = shift;
514 my $framework = shift;
516 # optional callback to monitor status
518 my $progress_interval = 0;
519 my $progress_callback = undef;
521 $progress_interval = shift;
522 $progress_callback = shift;
523 $progress_interval = 0 unless $progress_interval =~ /^\d+$/ and $progress_interval > 0;
524 $progress_interval = 0 unless 'CODE' eq ref $progress_callback;
530 my $num_items_added = 0;
531 my $num_items_errored = 0;
533 # commit (i.e., save, all records in the batch)
534 SetImportBatchStatus('importing');
535 my $overlay_action = GetImportBatchOverlayAction($batch_id);
536 my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
537 my $item_action = GetImportBatchItemAction($batch_id);
540 my $dbh = C4::Context->dbh;
541 my $sth = $dbh->prepare("SELECT import_records.import_record_id, record_type, status, overlay_status, marc, encoding
543 LEFT JOIN import_auths ON (import_records.import_record_id=import_auths.import_record_id)
544 LEFT JOIN import_biblios ON (import_records.import_record_id=import_biblios.import_record_id)
545 WHERE import_batch_id = ?");
546 $sth->execute($batch_id);
548 while (my $rowref = $sth->fetchrow_hashref) {
549 $record_type = $rowref->{'record_type'};
551 if ($progress_interval and (0 == ($rec_num % $progress_interval))) {
552 &$progress_callback($rec_num);
554 if ($rowref->{'status'} eq 'error' or $rowref->{'status'} eq 'imported') {
559 my $marc_record = MARC::Record->new_from_usmarc($rowref->{'marc'});
561 if ($record_type eq 'biblio') {
562 # remove any item tags - rely on BatchCommitItems
563 ($item_tag,$item_subfield) = &GetMarcFromKohaField("items.itemnumber",'');
564 foreach my $item_field ($marc_record->field($item_tag)) {
565 $marc_record->delete_field($item_field);
569 my ($record_result, $item_result, $record_match) =
570 _get_commit_action($overlay_action, $nomatch_action, $item_action,
571 $rowref->{'overlay_status'}, $rowref->{'import_record_id'}, $record_type);
575 if ($record_result eq 'create_new') {
577 if ($record_type eq 'biblio') {
578 my $biblioitemnumber;
579 ($recordid, $biblioitemnumber) = AddBiblio($marc_record, $framework);
580 $query = "UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?";
581 if ($item_result eq 'create_new') {
582 my ($bib_items_added, $bib_items_errored) = BatchCommitItems($rowref->{'import_record_id'}, $recordid);
583 $num_items_added += $bib_items_added;
584 $num_items_errored += $bib_items_errored;
587 my $authid = AddAuthority($marc_record, undef, GuessAuthTypeCode($marc_record));
588 $query = "UPDATE import_auths SET matched_authid = ? WHERE import_record_id = ?";
590 my $sth = $dbh->prepare_cached($query);
591 $sth->execute($recordid, $rowref->{'import_record_id'});
593 SetImportRecordStatus($rowref->{'import_record_id'}, 'imported');
594 } elsif ($record_result eq 'replace') {
596 $recordid = $record_match;
598 if ($record_type eq 'biblio') {
599 my ($count, $oldbiblio) = GetBiblio($recordid);
600 $oldxml = GetXmlBiblio($recordid);
602 # remove item fields so that they don't get
603 # added again if record is reverted
604 my $old_marc = MARC::Record->new_from_xml(StripNonXmlChars($oldxml), 'UTF-8', $rowref->{'encoding'});
605 foreach my $item_field ($old_marc->field($item_tag)) {
606 $old_marc->delete_field($item_field);
608 $oldxml = $old_marc->as_xml();
610 ModBiblio($marc_record, $recordid, $oldbiblio->{'frameworkcode'});
611 $query = "UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?";
613 if ($item_result eq 'create_new') {
614 my ($bib_items_added, $bib_items_errored) = BatchCommitItems($rowref->{'import_record_id'}, $recordid);
615 $num_items_added += $bib_items_added;
616 $num_items_errored += $bib_items_errored;
619 my $oldxml = GetAuthorityXML($recordid);
621 ModAuthority($recordid, $marc_record, GuessAuthTypeCode($marc_record));
622 $query = "UPDATE import_auths SET matched_authid = ? WHERE import_record_id = ?";
624 my $sth = $dbh->prepare_cached("UPDATE import_records SET marcxml_old = ? WHERE import_record_id = ?");
625 $sth->execute($oldxml, $rowref->{'import_record_id'});
627 my $sth2 = $dbh->prepare_cached($query);
628 $sth2->execute($recordid, $rowref->{'import_record_id'});
630 SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'match_applied');
631 SetImportRecordStatus($rowref->{'import_record_id'}, 'imported');
632 } elsif ($record_result eq 'ignore') {
634 if ($record_type eq 'biblio' and defined $recordid and $item_result eq 'create_new') {
635 my ($bib_items_added, $bib_items_errored) = BatchCommitItems($rowref->{'import_record_id'}, $recordid);
636 $num_items_added += $bib_items_added;
637 $num_items_errored += $bib_items_errored;
638 # still need to record the matched biblionumber so that the
639 # items can be reverted
640 my $sth2 = $dbh->prepare_cached("UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?");
641 $sth2->execute($recordid, $rowref->{'import_record_id'});
642 SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'match_applied');
644 SetImportRecordStatus($rowref->{'import_record_id'}, 'ignored');
648 SetImportBatchStatus($batch_id, 'imported');
649 return ($num_added, $num_updated, $num_items_added, $num_items_errored, $num_ignored);
652 =head2 BatchCommitItems
654 ($num_items_added, $num_items_errored) =
655 BatchCommitItems($import_record_id, $biblionumber);
659 sub BatchCommitItems {
660 my ($import_record_id, $biblionumber) = @_;
662 my $dbh = C4::Context->dbh;
664 my $num_items_added = 0;
665 my $num_items_errored = 0;
666 my $sth = $dbh->prepare("SELECT import_items_id, import_items.marcxml, encoding
668 JOIN import_records USING (import_record_id)
669 WHERE import_record_id = ?
670 ORDER BY import_items_id");
671 $sth->bind_param(1, $import_record_id);
673 while (my $row = $sth->fetchrow_hashref()) {
674 my $item_marc = MARC::Record->new_from_xml(StripNonXmlChars($row->{'marcxml'}), 'UTF-8', $row->{'encoding'});
675 # FIXME - duplicate barcode check needs to become part of AddItemFromMarc()
676 my $item = TransformMarcToKoha($dbh, $item_marc);
677 my $duplicate_barcode = exists($item->{'barcode'}) && GetItemnumberFromBarcode($item->{'barcode'});
678 if ($duplicate_barcode) {
679 my $updsth = $dbh->prepare("UPDATE import_items SET status = ?, import_error = ? WHERE import_items_id = ?");
680 $updsth->bind_param(1, 'error');
681 $updsth->bind_param(2, 'duplicate item barcode');
682 $updsth->bind_param(3, $row->{'import_items_id'});
684 $num_items_errored++;
686 my ($item_biblionumber, $biblioitemnumber, $itemnumber) = AddItemFromMarc($item_marc, $biblionumber);
687 my $updsth = $dbh->prepare("UPDATE import_items SET status = ?, itemnumber = ? WHERE import_items_id = ?");
688 $updsth->bind_param(1, 'imported');
689 $updsth->bind_param(2, $itemnumber);
690 $updsth->bind_param(3, $row->{'import_items_id'});
697 return ($num_items_added, $num_items_errored);
700 =head2 BatchRevertRecords
702 my ($num_deleted, $num_errors, $num_reverted, $num_items_deleted,
703 $num_ignored) = BatchRevertRecords($batch_id);
707 sub BatchRevertRecords {
708 my $batch_id = shift;
713 my $num_reverted = 0;
715 my $num_items_deleted = 0;
716 # commit (i.e., save, all records in the batch)
717 SetImportBatchStatus('reverting');
718 my $overlay_action = GetImportBatchOverlayAction($batch_id);
719 my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
720 my $dbh = C4::Context->dbh;
721 my $sth = $dbh->prepare("SELECT import_records.import_record_id, record_type, status, overlay_status, marcxml_old, encoding, matched_biblionumber, matched_authid
723 LEFT JOIN import_auths ON (import_records.import_record_id=import_auths.import_record_id)
724 LEFT JOIN import_biblios ON (import_records.import_record_id=import_biblios.import_record_id)
725 WHERE import_batch_id = ?");
726 $sth->execute($batch_id);
727 while (my $rowref = $sth->fetchrow_hashref) {
728 $record_type = $rowref->{'record_type'};
729 if ($rowref->{'status'} eq 'error' or $rowref->{'status'} eq 'reverted') {
734 my $record_result = _get_revert_action($overlay_action, $rowref->{'overlay_status'}, $rowref->{'status'});
736 if ($record_result eq 'delete') {
738 if ($record_type eq 'biblio') {
739 $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
740 $error = DelBiblio($rowref->{'matched_biblionumber'});
742 my $deletedauthid = DelAuthority($rowref->{'matched_authid'});
744 if (defined $error) {
748 SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
750 } elsif ($record_result eq 'restore') {
752 my $old_record = MARC::Record->new_from_xml(StripNonXmlChars($rowref->{'marcxml_old'}), 'UTF-8', $rowref->{'encoding'});
753 if ($record_type eq 'biblio') {
754 my $biblionumber = $rowref->{'matched_biblionumber'};
755 my ($count, $oldbiblio) = GetBiblio($biblionumber);
756 $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
757 ModBiblio($old_record, $biblionumber, $oldbiblio->{'frameworkcode'});
759 my $authid = $rowref->{'matched_authid'};
760 ModAuthority($authid, $old_record, GuessAuthTypeCode($old_record));
762 SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
763 } elsif ($record_result eq 'ignore') {
764 if ($record_type eq 'biblio') {
765 $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
767 SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
770 if ($record_type eq 'biblio') {
771 # remove matched_biblionumber only if there is no 'imported' item left
772 $query = "UPDATE import_biblios SET matched_biblionumber = NULL WHERE import_record_id = ?";
773 $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')";
775 $query = "UPDATE import_auths SET matched_authid = NULL WHERE import_record_id = ?";
777 my $sth2 = $dbh->prepare_cached($query);
778 $sth2->execute($rowref->{'import_record_id'});
782 SetImportBatchStatus($batch_id, 'reverted');
783 return ($num_deleted, $num_errors, $num_reverted, $num_items_deleted, $num_ignored);
786 =head2 BatchRevertItems
788 my $num_items_deleted = BatchRevertItems($import_record_id, $biblionumber);
792 sub BatchRevertItems {
793 my ($import_record_id, $biblionumber) = @_;
795 my $dbh = C4::Context->dbh;
796 my $num_items_deleted = 0;
798 my $sth = $dbh->prepare_cached("SELECT import_items_id, itemnumber
800 JOIN items USING (itemnumber)
801 WHERE import_record_id = ?");
802 $sth->bind_param(1, $import_record_id);
804 while (my $row = $sth->fetchrow_hashref()) {
805 my $error = DelItemCheck($dbh, $biblionumber, $row->{'itemnumber'});
807 my $updsth = $dbh->prepare("UPDATE import_items SET status = ? WHERE import_items_id = ?");
808 $updsth->bind_param(1, 'reverted');
809 $updsth->bind_param(2, $row->{'import_items_id'});
812 $num_items_deleted++;
819 return $num_items_deleted;
824 CleanBatch($batch_id)
826 Deletes all staged records from the import batch
827 and sets the status of the batch to 'cleaned'. Note
828 that deleting a stage record does *not* affect
829 any record that has been committed to the database.
834 my $batch_id = shift;
835 return unless defined $batch_id;
837 C4::Context->dbh->do('DELETE FROM import_records WHERE import_batch_id = ?', {}, $batch_id);
838 SetImportBatchStatus($batch_id, 'cleaned');
841 =head2 GetAllImportBatches
843 my $results = GetAllImportBatches();
845 Returns a references to an array of hash references corresponding
846 to all import_batches rows (of batch_type 'batch'), sorted in
847 ascending order by import_batch_id.
851 sub GetAllImportBatches {
852 my $dbh = C4::Context->dbh;
853 my $sth = $dbh->prepare_cached("SELECT * FROM import_batches
854 WHERE batch_type IN ('batch', 'webservice')
855 ORDER BY import_batch_id ASC");
859 while (my $row = $sth->fetchrow_hashref) {
860 push @$results, $row;
866 =head2 GetStagedWebserviceBatches
868 my $batch_ids = GetStagedWebserviceBatches();
870 Returns a references to an array of batch id's
871 of batch_type 'webservice' that are not imported
875 my $PENDING_WEBSERVICE_BATCHES_QRY = <<EOQ;
876 SELECT import_batch_id FROM import_batches
877 WHERE batch_type = 'webservice'
878 AND import_status = 'staged'
880 sub GetStagedWebserviceBatches {
881 my $dbh = C4::Context->dbh;
882 return $dbh->selectcol_arrayref($PENDING_WEBSERVICE_BATCHES_QRY);
885 =head2 GetImportBatchRangeDesc
887 my $results = GetImportBatchRangeDesc($offset, $results_per_group);
889 Returns a reference to an array of hash references corresponding to
890 import_batches rows (sorted in descending order by import_batch_id)
891 start at the given offset.
895 sub GetImportBatchRangeDesc {
896 my ($offset, $results_per_group) = @_;
898 my $dbh = C4::Context->dbh;
899 my $query = "SELECT * FROM import_batches
900 WHERE batch_type IN ('batch', 'webservice')
901 ORDER BY import_batch_id DESC";
903 if ($results_per_group){
904 $query .= " LIMIT ?";
905 push(@params, $results_per_group);
908 $query .= " OFFSET ?";
909 push(@params, $offset);
911 my $sth = $dbh->prepare_cached($query);
912 $sth->execute(@params);
913 my $results = $sth->fetchall_arrayref({});
918 =head2 GetItemNumbersFromImportBatch
920 my @itemsnos = GetItemNumbersFromImportBatch($batch_id);
924 sub GetItemNumbersFromImportBatch {
926 my $dbh = C4::Context->dbh;
927 my $sth = $dbh->prepare("SELECT itemnumber FROM import_batches,import_records,import_items WHERE import_batches.import_batch_id=import_records.import_batch_id AND import_records.import_record_id=import_items.import_record_id AND import_batches.import_batch_id=?");
928 $sth->execute($batch_id);
930 while ( my ($itm) = $sth->fetchrow_array ) {
936 =head2 GetNumberOfImportBatches
938 my $count = GetNumberOfImportBatches();
942 sub GetNumberOfNonZ3950ImportBatches {
943 my $dbh = C4::Context->dbh;
944 my $sth = $dbh->prepare("SELECT COUNT(*) FROM import_batches WHERE batch_type != 'z3950'");
946 my ($count) = $sth->fetchrow_array();
951 =head2 GetImportRecordsRange
953 my $results = GetImportRecordsRange($batch_id, $offset, $results_per_group);
955 Returns a reference to an array of hash references corresponding to
956 import_biblios/import_auths/import_records rows for a given batch
957 starting at the given offset.
961 sub GetImportRecordsRange {
962 my ($batch_id, $offset, $results_per_group, $status) = @_;
964 my $dbh = C4::Context->dbh;
965 my $query = "SELECT title, author, isbn, issn, authorized_heading, import_records.import_record_id,
966 record_sequence, status, overlay_status,
967 matched_biblionumber, matched_authid, record_type
969 LEFT JOIN import_auths ON (import_records.import_record_id=import_auths.import_record_id)
970 LEFT JOIN import_biblios ON (import_records.import_record_id=import_biblios.import_record_id)
971 WHERE import_batch_id = ?";
973 push(@params, $batch_id);
975 $query .= " AND status=?";
976 push(@params,$status);
978 $query.=" ORDER BY import_record_id";
980 if($results_per_group){
981 $query .= " LIMIT ?";
982 push(@params, $results_per_group);
985 $query .= " OFFSET ?";
986 push(@params, $offset);
988 my $sth = $dbh->prepare_cached($query);
989 $sth->execute(@params);
990 my $results = $sth->fetchall_arrayref({});
996 =head2 GetBestRecordMatch
998 my $record_id = GetBestRecordMatch($import_record_id);
1002 sub GetBestRecordMatch {
1003 my ($import_record_id) = @_;
1005 my $dbh = C4::Context->dbh;
1006 my $sth = $dbh->prepare("SELECT candidate_match_id
1007 FROM import_record_matches
1008 WHERE import_record_id = ?
1009 ORDER BY score DESC, candidate_match_id DESC");
1010 $sth->execute($import_record_id);
1011 my ($record_id) = $sth->fetchrow_array();
1016 =head2 GetImportBatchStatus
1018 my $status = GetImportBatchStatus($batch_id);
1022 sub GetImportBatchStatus {
1023 my ($batch_id) = @_;
1025 my $dbh = C4::Context->dbh;
1026 my $sth = $dbh->prepare("SELECT import_status FROM import_batches WHERE import_batch_id = ?");
1027 $sth->execute($batch_id);
1028 my ($status) = $sth->fetchrow_array();
1034 =head2 SetImportBatchStatus
1036 SetImportBatchStatus($batch_id, $new_status);
1040 sub SetImportBatchStatus {
1041 my ($batch_id, $new_status) = @_;
1043 my $dbh = C4::Context->dbh;
1044 my $sth = $dbh->prepare("UPDATE import_batches SET import_status = ? WHERE import_batch_id = ?");
1045 $sth->execute($new_status, $batch_id);
1050 =head2 GetImportBatchOverlayAction
1052 my $overlay_action = GetImportBatchOverlayAction($batch_id);
1056 sub GetImportBatchOverlayAction {
1057 my ($batch_id) = @_;
1059 my $dbh = C4::Context->dbh;
1060 my $sth = $dbh->prepare("SELECT overlay_action FROM import_batches WHERE import_batch_id = ?");
1061 $sth->execute($batch_id);
1062 my ($overlay_action) = $sth->fetchrow_array();
1064 return $overlay_action;
1069 =head2 SetImportBatchOverlayAction
1071 SetImportBatchOverlayAction($batch_id, $new_overlay_action);
1075 sub SetImportBatchOverlayAction {
1076 my ($batch_id, $new_overlay_action) = @_;
1078 my $dbh = C4::Context->dbh;
1079 my $sth = $dbh->prepare("UPDATE import_batches SET overlay_action = ? WHERE import_batch_id = ?");
1080 $sth->execute($new_overlay_action, $batch_id);
1085 =head2 GetImportBatchNoMatchAction
1087 my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
1091 sub GetImportBatchNoMatchAction {
1092 my ($batch_id) = @_;
1094 my $dbh = C4::Context->dbh;
1095 my $sth = $dbh->prepare("SELECT nomatch_action FROM import_batches WHERE import_batch_id = ?");
1096 $sth->execute($batch_id);
1097 my ($nomatch_action) = $sth->fetchrow_array();
1099 return $nomatch_action;
1104 =head2 SetImportBatchNoMatchAction
1106 SetImportBatchNoMatchAction($batch_id, $new_nomatch_action);
1110 sub SetImportBatchNoMatchAction {
1111 my ($batch_id, $new_nomatch_action) = @_;
1113 my $dbh = C4::Context->dbh;
1114 my $sth = $dbh->prepare("UPDATE import_batches SET nomatch_action = ? WHERE import_batch_id = ?");
1115 $sth->execute($new_nomatch_action, $batch_id);
1120 =head2 GetImportBatchItemAction
1122 my $item_action = GetImportBatchItemAction($batch_id);
1126 sub GetImportBatchItemAction {
1127 my ($batch_id) = @_;
1129 my $dbh = C4::Context->dbh;
1130 my $sth = $dbh->prepare("SELECT item_action FROM import_batches WHERE import_batch_id = ?");
1131 $sth->execute($batch_id);
1132 my ($item_action) = $sth->fetchrow_array();
1134 return $item_action;
1139 =head2 SetImportBatchItemAction
1141 SetImportBatchItemAction($batch_id, $new_item_action);
1145 sub SetImportBatchItemAction {
1146 my ($batch_id, $new_item_action) = @_;
1148 my $dbh = C4::Context->dbh;
1149 my $sth = $dbh->prepare("UPDATE import_batches SET item_action = ? WHERE import_batch_id = ?");
1150 $sth->execute($new_item_action, $batch_id);
1155 =head2 GetImportBatchMatcher
1157 my $matcher_id = GetImportBatchMatcher($batch_id);
1161 sub GetImportBatchMatcher {
1162 my ($batch_id) = @_;
1164 my $dbh = C4::Context->dbh;
1165 my $sth = $dbh->prepare("SELECT matcher_id FROM import_batches WHERE import_batch_id = ?");
1166 $sth->execute($batch_id);
1167 my ($matcher_id) = $sth->fetchrow_array();
1174 =head2 SetImportBatchMatcher
1176 SetImportBatchMatcher($batch_id, $new_matcher_id);
1180 sub SetImportBatchMatcher {
1181 my ($batch_id, $new_matcher_id) = @_;
1183 my $dbh = C4::Context->dbh;
1184 my $sth = $dbh->prepare("UPDATE import_batches SET matcher_id = ? WHERE import_batch_id = ?");
1185 $sth->execute($new_matcher_id, $batch_id);
1190 =head2 GetImportRecordOverlayStatus
1192 my $overlay_status = GetImportRecordOverlayStatus($import_record_id);
1196 sub GetImportRecordOverlayStatus {
1197 my ($import_record_id) = @_;
1199 my $dbh = C4::Context->dbh;
1200 my $sth = $dbh->prepare("SELECT overlay_status FROM import_records WHERE import_record_id = ?");
1201 $sth->execute($import_record_id);
1202 my ($overlay_status) = $sth->fetchrow_array();
1204 return $overlay_status;
1209 =head2 SetImportRecordOverlayStatus
1211 SetImportRecordOverlayStatus($import_record_id, $new_overlay_status);
1215 sub SetImportRecordOverlayStatus {
1216 my ($import_record_id, $new_overlay_status) = @_;
1218 my $dbh = C4::Context->dbh;
1219 my $sth = $dbh->prepare("UPDATE import_records SET overlay_status = ? WHERE import_record_id = ?");
1220 $sth->execute($new_overlay_status, $import_record_id);
1225 =head2 GetImportRecordStatus
1227 my $overlay_status = GetImportRecordStatus($import_record_id);
1231 sub GetImportRecordStatus {
1232 my ($import_record_id) = @_;
1234 my $dbh = C4::Context->dbh;
1235 my $sth = $dbh->prepare("SELECT status FROM import_records WHERE import_record_id = ?");
1236 $sth->execute($import_record_id);
1237 my ($overlay_status) = $sth->fetchrow_array();
1239 return $overlay_status;
1244 =head2 SetImportRecordStatus
1246 SetImportRecordStatus($import_record_id, $new_overlay_status);
1250 sub SetImportRecordStatus {
1251 my ($import_record_id, $new_overlay_status) = @_;
1253 my $dbh = C4::Context->dbh;
1254 my $sth = $dbh->prepare("UPDATE import_records SET status = ? WHERE import_record_id = ?");
1255 $sth->execute($new_overlay_status, $import_record_id);
1260 =head2 GetImportRecordMatches
1262 my $results = GetImportRecordMatches($import_record_id, $best_only);
1266 sub GetImportRecordMatches {
1267 my $import_record_id = shift;
1268 my $best_only = @_ ? shift : 0;
1270 my $dbh = C4::Context->dbh;
1271 # FIXME currently biblio only
1272 my $sth = $dbh->prepare_cached("SELECT title, author, biblionumber,
1273 candidate_match_id, score, record_type
1275 JOIN import_record_matches USING (import_record_id)
1276 LEFT JOIN biblio ON (biblionumber = candidate_match_id)
1277 WHERE import_record_id = ?
1278 ORDER BY score DESC, biblionumber DESC");
1279 $sth->bind_param(1, $import_record_id);
1282 while (my $row = $sth->fetchrow_hashref) {
1283 if ($row->{'record_type'} eq 'auth') {
1284 $row->{'authorized_heading'} = GetAuthorizedHeading( { authid => $row->{'candidate_match_id'} } );
1286 next if ($row->{'record_type'} eq 'biblio' && not $row->{'biblionumber'});
1287 push @$results, $row;
1297 =head2 SetImportRecordMatches
1299 SetImportRecordMatches($import_record_id, @matches);
1303 sub SetImportRecordMatches {
1304 my $import_record_id = shift;
1307 my $dbh = C4::Context->dbh;
1308 my $delsth = $dbh->prepare("DELETE FROM import_record_matches WHERE import_record_id = ?");
1309 $delsth->execute($import_record_id);
1312 my $sth = $dbh->prepare("INSERT INTO import_record_matches (import_record_id, candidate_match_id, score)
1314 foreach my $match (@matches) {
1315 $sth->execute($import_record_id, $match->{'record_id'}, $match->{'score'});
1320 # internal functions
1322 sub _create_import_record {
1323 my ($batch_id, $record_sequence, $marc_record, $record_type, $encoding, $z3950random) = @_;
1325 my $dbh = C4::Context->dbh;
1326 my $sth = $dbh->prepare("INSERT INTO import_records (import_batch_id, record_sequence, marc, marcxml,
1327 record_type, encoding, z3950random)
1328 VALUES (?, ?, ?, ?, ?, ?, ?)");
1329 $sth->execute($batch_id, $record_sequence, $marc_record->as_usmarc(), $marc_record->as_xml(),
1330 $record_type, $encoding, $z3950random);
1331 my $import_record_id = $dbh->{'mysql_insertid'};
1333 return $import_record_id;
1336 sub _update_import_record_marc {
1337 my ($import_record_id, $marc_record) = @_;
1339 my $dbh = C4::Context->dbh;
1340 my $sth = $dbh->prepare("UPDATE import_records SET marc = ?, marcxml = ?
1341 WHERE import_record_id = ?");
1342 $sth->execute($marc_record->as_usmarc(), $marc_record->as_xml(C4::Context->preference('marcflavour')), $import_record_id);
1346 sub _add_auth_fields {
1347 my ($import_record_id, $marc_record) = @_;
1350 if ($marc_record->field('001')) {
1351 $controlnumber = $marc_record->field('001')->data();
1353 my $authorized_heading = GetAuthorizedHeading($marc_record);
1354 my $dbh = C4::Context->dbh;
1355 my $sth = $dbh->prepare("INSERT INTO import_auths (import_record_id, controlnumber, authorized_heading) VALUES (?, ?, ?)");
1356 $sth->execute($import_record_id, $controlnumber, $authorized_heading);
1360 sub _add_biblio_fields {
1361 my ($import_record_id, $marc_record) = @_;
1363 my ($title, $author, $isbn, $issn) = _parse_biblio_fields($marc_record);
1364 my $dbh = C4::Context->dbh;
1365 # FIXME no controlnumber, originalsource
1366 $isbn = C4::Koha::_isbn_cleanup($isbn); # FIXME C4::Koha::_isbn_cleanup should be made public
1367 my $sth = $dbh->prepare("INSERT INTO import_biblios (import_record_id, title, author, isbn, issn) VALUES (?, ?, ?, ?, ?)");
1368 $sth->execute($import_record_id, $title, $author, $isbn, $issn);
1373 sub _update_biblio_fields {
1374 my ($import_record_id, $marc_record) = @_;
1376 my ($title, $author, $isbn, $issn) = _parse_biblio_fields($marc_record);
1377 my $dbh = C4::Context->dbh;
1378 # FIXME no controlnumber, originalsource
1379 # FIXME 2 - should regularize normalization of ISBN wherever it is done
1383 my $sth = $dbh->prepare("UPDATE import_biblios SET title = ?, author = ?, isbn = ?, issn = ?
1384 WHERE import_record_id = ?");
1385 $sth->execute($title, $author, $isbn, $issn, $import_record_id);
1389 sub _parse_biblio_fields {
1390 my ($marc_record) = @_;
1392 my $dbh = C4::Context->dbh;
1393 my $bibliofields = TransformMarcToKoha($dbh, $marc_record, '');
1394 return ($bibliofields->{'title'}, $bibliofields->{'author'}, $bibliofields->{'isbn'}, $bibliofields->{'issn'});
1398 sub _update_batch_record_counts {
1399 my ($batch_id) = @_;
1401 my $dbh = C4::Context->dbh;
1402 my $sth = $dbh->prepare_cached("UPDATE import_batches SET
1406 WHERE import_batch_id = import_batches.import_batch_id
1407 AND record_type = 'biblio'),
1411 JOIN import_items USING (import_record_id)
1412 WHERE import_batch_id = import_batches.import_batch_id
1413 AND record_type = 'biblio')
1414 WHERE import_batch_id = ?");
1415 $sth->bind_param(1, $batch_id);
1420 sub _get_commit_action {
1421 my ($overlay_action, $nomatch_action, $item_action, $overlay_status, $import_record_id, $record_type) = @_;
1423 if ($record_type eq 'biblio') {
1424 my ($bib_result, $bib_match, $item_result);
1426 if ($overlay_status ne 'no_match') {
1427 $bib_match = GetBestRecordMatch($import_record_id);
1428 if ($overlay_action eq 'replace') {
1429 $bib_result = defined($bib_match) ? 'replace' : 'create_new';
1430 } elsif ($overlay_action eq 'create_new') {
1431 $bib_result = 'create_new';
1432 } elsif ($overlay_action eq 'ignore') {
1433 $bib_result = 'ignore';
1435 $item_result = ($item_action eq 'always_add' or $item_action eq 'add_only_for_matches') ? 'create_new' : 'ignore';
1437 $bib_result = $nomatch_action;
1438 $item_result = ($item_action eq 'always_add' or $item_action eq 'add_only_for_new') ? 'create_new' : 'ignore';
1440 return ($bib_result, $item_result, $bib_match);
1441 } else { # must be auths
1442 my ($auth_result, $auth_match);
1444 if ($overlay_status ne 'no_match') {
1445 $auth_match = GetBestRecordMatch($import_record_id);
1446 if ($overlay_action eq 'replace') {
1447 $auth_result = defined($auth_match) ? 'replace' : 'create_new';
1448 } elsif ($overlay_action eq 'create_new') {
1449 $auth_result = 'create_new';
1450 } elsif ($overlay_action eq 'ignore') {
1451 $auth_result = 'ignore';
1454 $auth_result = $nomatch_action;
1457 return ($auth_result, undef, $auth_match);
1462 sub _get_revert_action {
1463 my ($overlay_action, $overlay_status, $status) = @_;
1467 if ($status eq 'ignored') {
1468 $bib_result = 'ignore';
1470 if ($overlay_action eq 'create_new') {
1471 $bib_result = 'delete';
1473 $bib_result = ($overlay_status eq 'match_applied') ? 'restore' : 'delete';
1484 Koha Development Team <http://koha-community.org/>
1486 Galen Charlton <galen.charlton@liblime.com>