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 record_type )) {
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, C4::Context->preference('marcflavour'));
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, C4::Context->preference('marcflavour'));
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, [$marc_type]);
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;
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, $z3950random, $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 ModAuthInBatch
304 ModAuthInBatch($import_record_id, $marc_record);
309 my ($import_record_id, $marc_record) = @_;
311 my $marcflavour = C4::Context->preference('marcflavour');
312 _update_import_record_marc($import_record_id, $marc_record, $marcflavour eq 'UNIMARC' ? 'UNIMARCAUTH' : 'USMARC');
316 =head2 BatchStageMarcRecords
318 ($batch_id, $num_records, $num_items, @invalid_records) =
319 BatchStageMarcRecords($record_type, $encoding, $marc_records, $file_name,
320 $comments, $branch_code, $parse_items,
322 $progress_interval, $progress_callback);
326 sub BatchStageMarcRecords {
327 my $record_type = shift;
328 my $encoding = shift;
329 my $marc_records = shift;
330 my $file_name = shift;
331 my $comments = shift;
332 my $branch_code = shift;
333 my $parse_items = shift;
334 my $leave_as_staging = shift;
336 # optional callback to monitor status
338 my $progress_interval = 0;
339 my $progress_callback = undef;
341 $progress_interval = shift;
342 $progress_callback = shift;
343 $progress_interval = 0 unless $progress_interval =~ /^\d+$/ and $progress_interval > 0;
344 $progress_interval = 0 unless 'CODE' eq ref $progress_callback;
347 my $batch_id = AddImportBatch( {
348 overlay_action => 'create_new',
349 import_status => 'staging',
350 batch_type => 'batch',
351 file_name => $file_name,
352 comments => $comments,
353 record_type => $record_type,
356 SetImportBatchItemAction($batch_id, 'always_add');
358 SetImportBatchItemAction($batch_id, 'ignore');
361 my $marc_type = C4::Context->preference('marcflavour');
362 $marc_type .= 'AUTH' if ($marc_type eq 'UNIMARC' && $record_type eq 'auth');
363 my @invalid_records = ();
366 # FIXME - for now, we're dealing only with bibs
368 foreach my $marc_blob (split(/\x1D/, $marc_records)) {
369 $marc_blob =~ s/^\s+//g;
370 $marc_blob =~ s/\s+$//g;
371 next unless $marc_blob;
373 if ($progress_interval and (0 == ($rec_num % $progress_interval))) {
374 &$progress_callback($rec_num);
376 my ($marc_record, $charset_guessed, $char_errors) =
377 MarcToUTF8Record($marc_blob, $marc_type, $encoding);
379 $encoding = $charset_guessed unless $encoding;
381 my $import_record_id;
382 if (scalar($marc_record->fields()) == 0) {
383 push @invalid_records, $marc_blob;
386 if ($record_type eq 'biblio') {
387 $import_record_id = AddBiblioToBatch($batch_id, $rec_num, $marc_record, $encoding, int(rand(99999)), 0);
389 my @import_items_ids = AddItemsToImportBiblio($batch_id, $import_record_id, $marc_record, 0);
390 $num_items += scalar(@import_items_ids);
392 } elsif ($record_type eq 'auth') {
393 $import_record_id = AddAuthToBatch($batch_id, $rec_num, $marc_record, $encoding, int(rand(99999)), 0, $marc_type);
397 unless ($leave_as_staging) {
398 SetImportBatchStatus($batch_id, 'staged');
400 # FIXME branch_code, number of bibs, number of items
401 _update_batch_record_counts($batch_id);
402 return ($batch_id, $num_valid, $num_items, @invalid_records);
405 =head2 AddItemsToImportBiblio
407 my @import_items_ids = AddItemsToImportBiblio($batch_id,
408 $import_record_id, $marc_record, $update_counts);
412 sub AddItemsToImportBiblio {
413 my $batch_id = shift;
414 my $import_record_id = shift;
415 my $marc_record = shift;
416 my $update_counts = @_ ? shift : 0;
418 my @import_items_ids = ();
420 my $dbh = C4::Context->dbh;
421 my ($item_tag,$item_subfield) = &GetMarcFromKohaField("items.itemnumber",'');
422 foreach my $item_field ($marc_record->field($item_tag)) {
423 my $item_marc = MARC::Record->new();
424 $item_marc->leader("00000 a "); # must set Leader/09 to 'a'
425 $item_marc->append_fields($item_field);
426 $marc_record->delete_field($item_field);
427 my $sth = $dbh->prepare_cached("INSERT INTO import_items (import_record_id, status, marcxml)
429 $sth->bind_param(1, $import_record_id);
430 $sth->bind_param(2, 'staged');
431 $sth->bind_param(3, $item_marc->as_xml());
433 push @import_items_ids, $dbh->{'mysql_insertid'};
437 if ($#import_items_ids > -1) {
438 _update_batch_record_counts($batch_id) if $update_counts;
439 _update_import_record_marc($import_record_id, $marc_record, C4::Context->preference('marcflavour'));
441 return @import_items_ids;
444 =head2 BatchFindDuplicates
446 my $num_with_matches = BatchFindDuplicates($batch_id, $matcher,
447 $max_matches, $progress_interval, $progress_callback);
449 Goes through the records loaded in the batch and attempts to
450 find duplicates for each one. Sets the matching status
451 of each record to "no_match" or "auto_match" as appropriate.
453 The $max_matches parameter is optional; if it is not supplied,
456 The $progress_interval and $progress_callback parameters are
457 optional; if both are supplied, the sub referred to by
458 $progress_callback will be invoked every $progress_interval
459 records using the number of records processed as the
464 sub BatchFindDuplicates {
465 my $batch_id = shift;
467 my $max_matches = @_ ? shift : 10;
469 # optional callback to monitor status
471 my $progress_interval = 0;
472 my $progress_callback = undef;
474 $progress_interval = shift;
475 $progress_callback = shift;
476 $progress_interval = 0 unless $progress_interval =~ /^\d+$/ and $progress_interval > 0;
477 $progress_interval = 0 unless 'CODE' eq ref $progress_callback;
480 my $dbh = C4::Context->dbh;
482 my $sth = $dbh->prepare("SELECT import_record_id, record_type, marc
484 WHERE import_batch_id = ?");
485 $sth->execute($batch_id);
486 my $num_with_matches = 0;
488 while (my $rowref = $sth->fetchrow_hashref) {
490 if ($progress_interval and (0 == ($rec_num % $progress_interval))) {
491 &$progress_callback($rec_num);
493 my $marc_record = MARC::Record->new_from_usmarc($rowref->{'marc'});
495 if (defined $matcher) {
496 @matches = $matcher->get_matches($marc_record, $max_matches);
498 if (scalar(@matches) > 0) {
500 SetImportRecordMatches($rowref->{'import_record_id'}, @matches);
501 SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'auto_match');
503 SetImportRecordMatches($rowref->{'import_record_id'}, ());
504 SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'no_match');
508 return $num_with_matches;
511 =head2 BatchCommitRecords
513 my ($num_added, $num_updated, $num_items_added, $num_items_errored, $num_ignored) =
514 BatchCommitRecords($batch_id, $framework,
515 $progress_interval, $progress_callback);
519 sub BatchCommitRecords {
520 my $batch_id = shift;
521 my $framework = shift;
523 # optional callback to monitor status
525 my $progress_interval = 0;
526 my $progress_callback = undef;
528 $progress_interval = shift;
529 $progress_callback = shift;
530 $progress_interval = 0 unless $progress_interval =~ /^\d+$/ and $progress_interval > 0;
531 $progress_interval = 0 unless 'CODE' eq ref $progress_callback;
537 my $num_items_added = 0;
538 my $num_items_errored = 0;
540 # commit (i.e., save, all records in the batch)
541 SetImportBatchStatus('importing');
542 my $overlay_action = GetImportBatchOverlayAction($batch_id);
543 my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
544 my $item_action = GetImportBatchItemAction($batch_id);
547 my $dbh = C4::Context->dbh;
548 my $sth = $dbh->prepare("SELECT import_records.import_record_id, record_type, status, overlay_status, marc, encoding
550 LEFT JOIN import_auths ON (import_records.import_record_id=import_auths.import_record_id)
551 LEFT JOIN import_biblios ON (import_records.import_record_id=import_biblios.import_record_id)
552 WHERE import_batch_id = ?");
553 $sth->execute($batch_id);
554 my $marcflavour = C4::Context->preference('marcflavour');
556 while (my $rowref = $sth->fetchrow_hashref) {
557 $record_type = $rowref->{'record_type'};
559 if ($progress_interval and (0 == ($rec_num % $progress_interval))) {
560 &$progress_callback($rec_num);
562 if ($rowref->{'status'} eq 'error' or $rowref->{'status'} eq 'imported') {
568 if ($marcflavour eq 'UNIMARC' && $record_type eq 'auth') {
569 $marc_type = 'UNIMARCAUTH';
570 } elsif ($marcflavour eq 'UNIMARC') {
571 $marc_type = 'UNIMARC';
573 $marc_type = 'USMARC';
575 my $marc_record = MARC::Record->new_from_usmarc($rowref->{'marc'});
577 if ($record_type eq 'biblio') {
578 # remove any item tags - rely on BatchCommitItems
579 ($item_tag,$item_subfield) = &GetMarcFromKohaField("items.itemnumber",'');
580 foreach my $item_field ($marc_record->field($item_tag)) {
581 $marc_record->delete_field($item_field);
585 my ($record_result, $item_result, $record_match) =
586 _get_commit_action($overlay_action, $nomatch_action, $item_action,
587 $rowref->{'overlay_status'}, $rowref->{'import_record_id'}, $record_type);
591 if ($record_result eq 'create_new') {
593 if ($record_type eq 'biblio') {
594 my $biblioitemnumber;
595 ($recordid, $biblioitemnumber) = AddBiblio($marc_record, $framework);
596 $query = "UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?";
597 if ($item_result eq 'create_new') {
598 my ($bib_items_added, $bib_items_errored) = BatchCommitItems($rowref->{'import_record_id'}, $recordid);
599 $num_items_added += $bib_items_added;
600 $num_items_errored += $bib_items_errored;
603 $recordid = AddAuthority($marc_record, undef, GuessAuthTypeCode($marc_record));
604 $query = "UPDATE import_auths SET matched_authid = ? WHERE import_record_id = ?";
606 my $sth = $dbh->prepare_cached($query);
607 $sth->execute($recordid, $rowref->{'import_record_id'});
609 SetImportRecordStatus($rowref->{'import_record_id'}, 'imported');
610 } elsif ($record_result eq 'replace') {
612 $recordid = $record_match;
614 if ($record_type eq 'biblio') {
615 my ($count, $oldbiblio) = GetBiblio($recordid);
616 $oldxml = GetXmlBiblio($recordid);
618 # remove item fields so that they don't get
619 # added again if record is reverted
620 my $old_marc = MARC::Record->new_from_xml(StripNonXmlChars($oldxml), 'UTF-8', $rowref->{'encoding'}, $marc_type);
621 foreach my $item_field ($old_marc->field($item_tag)) {
622 $old_marc->delete_field($item_field);
624 $oldxml = $old_marc->as_xml($marc_type);
626 ModBiblio($marc_record, $recordid, $oldbiblio->{'frameworkcode'});
627 $query = "UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?";
629 if ($item_result eq 'create_new') {
630 my ($bib_items_added, $bib_items_errored) = BatchCommitItems($rowref->{'import_record_id'}, $recordid);
631 $num_items_added += $bib_items_added;
632 $num_items_errored += $bib_items_errored;
635 $oldxml = GetAuthorityXML($recordid);
637 ModAuthority($recordid, $marc_record, GuessAuthTypeCode($marc_record));
638 $query = "UPDATE import_auths SET matched_authid = ? WHERE import_record_id = ?";
640 my $sth = $dbh->prepare_cached("UPDATE import_records SET marcxml_old = ? WHERE import_record_id = ?");
641 $sth->execute($oldxml, $rowref->{'import_record_id'});
643 my $sth2 = $dbh->prepare_cached($query);
644 $sth2->execute($recordid, $rowref->{'import_record_id'});
646 SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'match_applied');
647 SetImportRecordStatus($rowref->{'import_record_id'}, 'imported');
648 } elsif ($record_result eq 'ignore') {
650 if ($record_type eq 'biblio' and defined $recordid and $item_result eq 'create_new') {
651 my ($bib_items_added, $bib_items_errored) = BatchCommitItems($rowref->{'import_record_id'}, $recordid);
652 $num_items_added += $bib_items_added;
653 $num_items_errored += $bib_items_errored;
654 # still need to record the matched biblionumber so that the
655 # items can be reverted
656 my $sth2 = $dbh->prepare_cached("UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?");
657 $sth2->execute($recordid, $rowref->{'import_record_id'});
658 SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'match_applied');
660 SetImportRecordStatus($rowref->{'import_record_id'}, 'ignored');
664 SetImportBatchStatus($batch_id, 'imported');
665 return ($num_added, $num_updated, $num_items_added, $num_items_errored, $num_ignored);
668 =head2 BatchCommitItems
670 ($num_items_added, $num_items_errored) =
671 BatchCommitItems($import_record_id, $biblionumber);
675 sub BatchCommitItems {
676 my ($import_record_id, $biblionumber) = @_;
678 my $dbh = C4::Context->dbh;
680 my $num_items_added = 0;
681 my $num_items_errored = 0;
682 my $sth = $dbh->prepare("SELECT import_items_id, import_items.marcxml, encoding
684 JOIN import_records USING (import_record_id)
685 WHERE import_record_id = ?
686 ORDER BY import_items_id");
687 $sth->bind_param(1, $import_record_id);
689 while (my $row = $sth->fetchrow_hashref()) {
690 my $item_marc = MARC::Record->new_from_xml(StripNonXmlChars($row->{'marcxml'}), 'UTF-8', $row->{'encoding'});
691 # FIXME - duplicate barcode check needs to become part of AddItemFromMarc()
692 my $item = TransformMarcToKoha($dbh, $item_marc);
693 my $duplicate_barcode = exists($item->{'barcode'}) && GetItemnumberFromBarcode($item->{'barcode'});
694 if ($duplicate_barcode) {
695 my $updsth = $dbh->prepare("UPDATE import_items SET status = ?, import_error = ? WHERE import_items_id = ?");
696 $updsth->bind_param(1, 'error');
697 $updsth->bind_param(2, 'duplicate item barcode');
698 $updsth->bind_param(3, $row->{'import_items_id'});
700 $num_items_errored++;
702 my ($item_biblionumber, $biblioitemnumber, $itemnumber) = AddItemFromMarc($item_marc, $biblionumber);
703 my $updsth = $dbh->prepare("UPDATE import_items SET status = ?, itemnumber = ? WHERE import_items_id = ?");
704 $updsth->bind_param(1, 'imported');
705 $updsth->bind_param(2, $itemnumber);
706 $updsth->bind_param(3, $row->{'import_items_id'});
713 return ($num_items_added, $num_items_errored);
716 =head2 BatchRevertRecords
718 my ($num_deleted, $num_errors, $num_reverted, $num_items_deleted,
719 $num_ignored) = BatchRevertRecords($batch_id);
723 sub BatchRevertRecords {
724 my $batch_id = shift;
729 my $num_reverted = 0;
731 my $num_items_deleted = 0;
732 # commit (i.e., save, all records in the batch)
733 SetImportBatchStatus('reverting');
734 my $overlay_action = GetImportBatchOverlayAction($batch_id);
735 my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
736 my $dbh = C4::Context->dbh;
737 my $sth = $dbh->prepare("SELECT import_records.import_record_id, record_type, status, overlay_status, marcxml_old, encoding, matched_biblionumber, matched_authid
739 LEFT JOIN import_auths ON (import_records.import_record_id=import_auths.import_record_id)
740 LEFT JOIN import_biblios ON (import_records.import_record_id=import_biblios.import_record_id)
741 WHERE import_batch_id = ?");
742 $sth->execute($batch_id);
744 my $marcflavour = C4::Context->preference('marcflavour');
745 while (my $rowref = $sth->fetchrow_hashref) {
746 $record_type = $rowref->{'record_type'};
747 if ($rowref->{'status'} eq 'error' or $rowref->{'status'} eq 'reverted') {
751 if ($marcflavour eq 'UNIMARC' && $record_type eq 'auth') {
752 $marc_type = 'UNIMARCAUTH';
753 } elsif ($marcflavour eq 'UNIMARC') {
754 $marc_type = 'UNIMARC';
756 $marc_type = 'USMARC';
759 my $record_result = _get_revert_action($overlay_action, $rowref->{'overlay_status'}, $rowref->{'status'});
761 if ($record_result eq 'delete') {
763 if ($record_type eq 'biblio') {
764 $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
765 $error = DelBiblio($rowref->{'matched_biblionumber'});
767 my $deletedauthid = DelAuthority($rowref->{'matched_authid'});
769 if (defined $error) {
773 SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
775 } elsif ($record_result eq 'restore') {
777 my $old_record = MARC::Record->new_from_xml(StripNonXmlChars($rowref->{'marcxml_old'}), 'UTF-8', $rowref->{'encoding'}, $marc_type);
778 if ($record_type eq 'biblio') {
779 my $biblionumber = $rowref->{'matched_biblionumber'};
780 my ($count, $oldbiblio) = GetBiblio($biblionumber);
781 $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
782 ModBiblio($old_record, $biblionumber, $oldbiblio->{'frameworkcode'});
784 my $authid = $rowref->{'matched_authid'};
785 ModAuthority($authid, $old_record, GuessAuthTypeCode($old_record));
787 SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
788 } elsif ($record_result eq 'ignore') {
789 if ($record_type eq 'biblio') {
790 $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
792 SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
795 if ($record_type eq 'biblio') {
796 # remove matched_biblionumber only if there is no 'imported' item left
797 $query = "UPDATE import_biblios SET matched_biblionumber = NULL WHERE import_record_id = ?";
798 $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')";
800 $query = "UPDATE import_auths SET matched_authid = NULL WHERE import_record_id = ?";
802 my $sth2 = $dbh->prepare_cached($query);
803 $sth2->execute($rowref->{'import_record_id'});
807 SetImportBatchStatus($batch_id, 'reverted');
808 return ($num_deleted, $num_errors, $num_reverted, $num_items_deleted, $num_ignored);
811 =head2 BatchRevertItems
813 my $num_items_deleted = BatchRevertItems($import_record_id, $biblionumber);
817 sub BatchRevertItems {
818 my ($import_record_id, $biblionumber) = @_;
820 my $dbh = C4::Context->dbh;
821 my $num_items_deleted = 0;
823 my $sth = $dbh->prepare_cached("SELECT import_items_id, itemnumber
825 JOIN items USING (itemnumber)
826 WHERE import_record_id = ?");
827 $sth->bind_param(1, $import_record_id);
829 while (my $row = $sth->fetchrow_hashref()) {
830 my $error = DelItemCheck($dbh, $biblionumber, $row->{'itemnumber'});
832 my $updsth = $dbh->prepare("UPDATE import_items SET status = ? WHERE import_items_id = ?");
833 $updsth->bind_param(1, 'reverted');
834 $updsth->bind_param(2, $row->{'import_items_id'});
837 $num_items_deleted++;
844 return $num_items_deleted;
849 CleanBatch($batch_id)
851 Deletes all staged records from the import batch
852 and sets the status of the batch to 'cleaned'. Note
853 that deleting a stage record does *not* affect
854 any record that has been committed to the database.
859 my $batch_id = shift;
860 return unless defined $batch_id;
862 C4::Context->dbh->do('DELETE FROM import_records WHERE import_batch_id = ?', {}, $batch_id);
863 SetImportBatchStatus($batch_id, 'cleaned');
866 =head2 GetAllImportBatches
868 my $results = GetAllImportBatches();
870 Returns a references to an array of hash references corresponding
871 to all import_batches rows (of batch_type 'batch'), sorted in
872 ascending order by import_batch_id.
876 sub GetAllImportBatches {
877 my $dbh = C4::Context->dbh;
878 my $sth = $dbh->prepare_cached("SELECT * FROM import_batches
879 WHERE batch_type IN ('batch', 'webservice')
880 ORDER BY import_batch_id ASC");
884 while (my $row = $sth->fetchrow_hashref) {
885 push @$results, $row;
891 =head2 GetStagedWebserviceBatches
893 my $batch_ids = GetStagedWebserviceBatches();
895 Returns a references to an array of batch id's
896 of batch_type 'webservice' that are not imported
900 my $PENDING_WEBSERVICE_BATCHES_QRY = <<EOQ;
901 SELECT import_batch_id FROM import_batches
902 WHERE batch_type = 'webservice'
903 AND import_status = 'staged'
905 sub GetStagedWebserviceBatches {
906 my $dbh = C4::Context->dbh;
907 return $dbh->selectcol_arrayref($PENDING_WEBSERVICE_BATCHES_QRY);
910 =head2 GetImportBatchRangeDesc
912 my $results = GetImportBatchRangeDesc($offset, $results_per_group);
914 Returns a reference to an array of hash references corresponding to
915 import_batches rows (sorted in descending order by import_batch_id)
916 start at the given offset.
920 sub GetImportBatchRangeDesc {
921 my ($offset, $results_per_group) = @_;
923 my $dbh = C4::Context->dbh;
924 my $query = "SELECT * FROM import_batches
925 WHERE batch_type IN ('batch', 'webservice')
926 ORDER BY import_batch_id DESC";
928 if ($results_per_group){
929 $query .= " LIMIT ?";
930 push(@params, $results_per_group);
933 $query .= " OFFSET ?";
934 push(@params, $offset);
936 my $sth = $dbh->prepare_cached($query);
937 $sth->execute(@params);
938 my $results = $sth->fetchall_arrayref({});
943 =head2 GetItemNumbersFromImportBatch
945 my @itemsnos = GetItemNumbersFromImportBatch($batch_id);
949 sub GetItemNumbersFromImportBatch {
951 my $dbh = C4::Context->dbh;
952 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=?");
953 $sth->execute($batch_id);
955 while ( my ($itm) = $sth->fetchrow_array ) {
961 =head2 GetNumberOfImportBatches
963 my $count = GetNumberOfImportBatches();
967 sub GetNumberOfNonZ3950ImportBatches {
968 my $dbh = C4::Context->dbh;
969 my $sth = $dbh->prepare("SELECT COUNT(*) FROM import_batches WHERE batch_type != 'z3950'");
971 my ($count) = $sth->fetchrow_array();
976 =head2 GetImportRecordsRange
978 my $results = GetImportRecordsRange($batch_id, $offset, $results_per_group);
980 Returns a reference to an array of hash references corresponding to
981 import_biblios/import_auths/import_records rows for a given batch
982 starting at the given offset.
986 sub GetImportRecordsRange {
987 my ($batch_id, $offset, $results_per_group, $status) = @_;
989 my $dbh = C4::Context->dbh;
990 my $query = "SELECT title, author, isbn, issn, authorized_heading, import_records.import_record_id,
991 record_sequence, status, overlay_status,
992 matched_biblionumber, matched_authid, record_type
994 LEFT JOIN import_auths ON (import_records.import_record_id=import_auths.import_record_id)
995 LEFT JOIN import_biblios ON (import_records.import_record_id=import_biblios.import_record_id)
996 WHERE import_batch_id = ?";
998 push(@params, $batch_id);
1000 $query .= " AND status=?";
1001 push(@params,$status);
1003 $query.=" ORDER BY import_record_id";
1005 if($results_per_group){
1006 $query .= " LIMIT ?";
1007 push(@params, $results_per_group);
1010 $query .= " OFFSET ?";
1011 push(@params, $offset);
1013 my $sth = $dbh->prepare_cached($query);
1014 $sth->execute(@params);
1015 my $results = $sth->fetchall_arrayref({});
1021 =head2 GetBestRecordMatch
1023 my $record_id = GetBestRecordMatch($import_record_id);
1027 sub GetBestRecordMatch {
1028 my ($import_record_id) = @_;
1030 my $dbh = C4::Context->dbh;
1031 my $sth = $dbh->prepare("SELECT candidate_match_id
1032 FROM import_record_matches
1033 WHERE import_record_id = ?
1034 ORDER BY score DESC, candidate_match_id DESC");
1035 $sth->execute($import_record_id);
1036 my ($record_id) = $sth->fetchrow_array();
1041 =head2 GetImportBatchStatus
1043 my $status = GetImportBatchStatus($batch_id);
1047 sub GetImportBatchStatus {
1048 my ($batch_id) = @_;
1050 my $dbh = C4::Context->dbh;
1051 my $sth = $dbh->prepare("SELECT import_status FROM import_batches WHERE import_batch_id = ?");
1052 $sth->execute($batch_id);
1053 my ($status) = $sth->fetchrow_array();
1059 =head2 SetImportBatchStatus
1061 SetImportBatchStatus($batch_id, $new_status);
1065 sub SetImportBatchStatus {
1066 my ($batch_id, $new_status) = @_;
1068 my $dbh = C4::Context->dbh;
1069 my $sth = $dbh->prepare("UPDATE import_batches SET import_status = ? WHERE import_batch_id = ?");
1070 $sth->execute($new_status, $batch_id);
1075 =head2 GetImportBatchOverlayAction
1077 my $overlay_action = GetImportBatchOverlayAction($batch_id);
1081 sub GetImportBatchOverlayAction {
1082 my ($batch_id) = @_;
1084 my $dbh = C4::Context->dbh;
1085 my $sth = $dbh->prepare("SELECT overlay_action FROM import_batches WHERE import_batch_id = ?");
1086 $sth->execute($batch_id);
1087 my ($overlay_action) = $sth->fetchrow_array();
1089 return $overlay_action;
1094 =head2 SetImportBatchOverlayAction
1096 SetImportBatchOverlayAction($batch_id, $new_overlay_action);
1100 sub SetImportBatchOverlayAction {
1101 my ($batch_id, $new_overlay_action) = @_;
1103 my $dbh = C4::Context->dbh;
1104 my $sth = $dbh->prepare("UPDATE import_batches SET overlay_action = ? WHERE import_batch_id = ?");
1105 $sth->execute($new_overlay_action, $batch_id);
1110 =head2 GetImportBatchNoMatchAction
1112 my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
1116 sub GetImportBatchNoMatchAction {
1117 my ($batch_id) = @_;
1119 my $dbh = C4::Context->dbh;
1120 my $sth = $dbh->prepare("SELECT nomatch_action FROM import_batches WHERE import_batch_id = ?");
1121 $sth->execute($batch_id);
1122 my ($nomatch_action) = $sth->fetchrow_array();
1124 return $nomatch_action;
1129 =head2 SetImportBatchNoMatchAction
1131 SetImportBatchNoMatchAction($batch_id, $new_nomatch_action);
1135 sub SetImportBatchNoMatchAction {
1136 my ($batch_id, $new_nomatch_action) = @_;
1138 my $dbh = C4::Context->dbh;
1139 my $sth = $dbh->prepare("UPDATE import_batches SET nomatch_action = ? WHERE import_batch_id = ?");
1140 $sth->execute($new_nomatch_action, $batch_id);
1145 =head2 GetImportBatchItemAction
1147 my $item_action = GetImportBatchItemAction($batch_id);
1151 sub GetImportBatchItemAction {
1152 my ($batch_id) = @_;
1154 my $dbh = C4::Context->dbh;
1155 my $sth = $dbh->prepare("SELECT item_action FROM import_batches WHERE import_batch_id = ?");
1156 $sth->execute($batch_id);
1157 my ($item_action) = $sth->fetchrow_array();
1159 return $item_action;
1164 =head2 SetImportBatchItemAction
1166 SetImportBatchItemAction($batch_id, $new_item_action);
1170 sub SetImportBatchItemAction {
1171 my ($batch_id, $new_item_action) = @_;
1173 my $dbh = C4::Context->dbh;
1174 my $sth = $dbh->prepare("UPDATE import_batches SET item_action = ? WHERE import_batch_id = ?");
1175 $sth->execute($new_item_action, $batch_id);
1180 =head2 GetImportBatchMatcher
1182 my $matcher_id = GetImportBatchMatcher($batch_id);
1186 sub GetImportBatchMatcher {
1187 my ($batch_id) = @_;
1189 my $dbh = C4::Context->dbh;
1190 my $sth = $dbh->prepare("SELECT matcher_id FROM import_batches WHERE import_batch_id = ?");
1191 $sth->execute($batch_id);
1192 my ($matcher_id) = $sth->fetchrow_array();
1199 =head2 SetImportBatchMatcher
1201 SetImportBatchMatcher($batch_id, $new_matcher_id);
1205 sub SetImportBatchMatcher {
1206 my ($batch_id, $new_matcher_id) = @_;
1208 my $dbh = C4::Context->dbh;
1209 my $sth = $dbh->prepare("UPDATE import_batches SET matcher_id = ? WHERE import_batch_id = ?");
1210 $sth->execute($new_matcher_id, $batch_id);
1215 =head2 GetImportRecordOverlayStatus
1217 my $overlay_status = GetImportRecordOverlayStatus($import_record_id);
1221 sub GetImportRecordOverlayStatus {
1222 my ($import_record_id) = @_;
1224 my $dbh = C4::Context->dbh;
1225 my $sth = $dbh->prepare("SELECT overlay_status FROM import_records WHERE import_record_id = ?");
1226 $sth->execute($import_record_id);
1227 my ($overlay_status) = $sth->fetchrow_array();
1229 return $overlay_status;
1234 =head2 SetImportRecordOverlayStatus
1236 SetImportRecordOverlayStatus($import_record_id, $new_overlay_status);
1240 sub SetImportRecordOverlayStatus {
1241 my ($import_record_id, $new_overlay_status) = @_;
1243 my $dbh = C4::Context->dbh;
1244 my $sth = $dbh->prepare("UPDATE import_records SET overlay_status = ? WHERE import_record_id = ?");
1245 $sth->execute($new_overlay_status, $import_record_id);
1250 =head2 GetImportRecordStatus
1252 my $overlay_status = GetImportRecordStatus($import_record_id);
1256 sub GetImportRecordStatus {
1257 my ($import_record_id) = @_;
1259 my $dbh = C4::Context->dbh;
1260 my $sth = $dbh->prepare("SELECT status FROM import_records WHERE import_record_id = ?");
1261 $sth->execute($import_record_id);
1262 my ($overlay_status) = $sth->fetchrow_array();
1264 return $overlay_status;
1269 =head2 SetImportRecordStatus
1271 SetImportRecordStatus($import_record_id, $new_overlay_status);
1275 sub SetImportRecordStatus {
1276 my ($import_record_id, $new_overlay_status) = @_;
1278 my $dbh = C4::Context->dbh;
1279 my $sth = $dbh->prepare("UPDATE import_records SET status = ? WHERE import_record_id = ?");
1280 $sth->execute($new_overlay_status, $import_record_id);
1285 =head2 GetImportRecordMatches
1287 my $results = GetImportRecordMatches($import_record_id, $best_only);
1291 sub GetImportRecordMatches {
1292 my $import_record_id = shift;
1293 my $best_only = @_ ? shift : 0;
1295 my $dbh = C4::Context->dbh;
1296 # FIXME currently biblio only
1297 my $sth = $dbh->prepare_cached("SELECT title, author, biblionumber,
1298 candidate_match_id, score, record_type
1300 JOIN import_record_matches USING (import_record_id)
1301 LEFT JOIN biblio ON (biblionumber = candidate_match_id)
1302 WHERE import_record_id = ?
1303 ORDER BY score DESC, biblionumber DESC");
1304 $sth->bind_param(1, $import_record_id);
1307 while (my $row = $sth->fetchrow_hashref) {
1308 if ($row->{'record_type'} eq 'auth') {
1309 $row->{'authorized_heading'} = GetAuthorizedHeading( { authid => $row->{'candidate_match_id'} } );
1311 next if ($row->{'record_type'} eq 'biblio' && not $row->{'biblionumber'});
1312 push @$results, $row;
1322 =head2 SetImportRecordMatches
1324 SetImportRecordMatches($import_record_id, @matches);
1328 sub SetImportRecordMatches {
1329 my $import_record_id = shift;
1332 my $dbh = C4::Context->dbh;
1333 my $delsth = $dbh->prepare("DELETE FROM import_record_matches WHERE import_record_id = ?");
1334 $delsth->execute($import_record_id);
1337 my $sth = $dbh->prepare("INSERT INTO import_record_matches (import_record_id, candidate_match_id, score)
1339 foreach my $match (@matches) {
1340 $sth->execute($import_record_id, $match->{'record_id'}, $match->{'score'});
1345 # internal functions
1347 sub _create_import_record {
1348 my ($batch_id, $record_sequence, $marc_record, $record_type, $encoding, $z3950random, $marc_type) = @_;
1350 my $dbh = C4::Context->dbh;
1351 my $sth = $dbh->prepare("INSERT INTO import_records (import_batch_id, record_sequence, marc, marcxml,
1352 record_type, encoding, z3950random)
1353 VALUES (?, ?, ?, ?, ?, ?, ?)");
1354 $sth->execute($batch_id, $record_sequence, $marc_record->as_usmarc(), $marc_record->as_xml($marc_type),
1355 $record_type, $encoding, $z3950random);
1356 my $import_record_id = $dbh->{'mysql_insertid'};
1358 return $import_record_id;
1361 sub _update_import_record_marc {
1362 my ($import_record_id, $marc_record, $marc_type) = @_;
1364 my $dbh = C4::Context->dbh;
1365 my $sth = $dbh->prepare("UPDATE import_records SET marc = ?, marcxml = ?
1366 WHERE import_record_id = ?");
1367 $sth->execute($marc_record->as_usmarc(), $marc_record->as_xml($marc_type), $import_record_id);
1371 sub _add_auth_fields {
1372 my ($import_record_id, $marc_record) = @_;
1375 if ($marc_record->field('001')) {
1376 $controlnumber = $marc_record->field('001')->data();
1378 my $authorized_heading = GetAuthorizedHeading({ record => $marc_record });
1379 my $dbh = C4::Context->dbh;
1380 my $sth = $dbh->prepare("INSERT INTO import_auths (import_record_id, control_number, authorized_heading) VALUES (?, ?, ?)");
1381 $sth->execute($import_record_id, $controlnumber, $authorized_heading);
1385 sub _add_biblio_fields {
1386 my ($import_record_id, $marc_record) = @_;
1388 my ($title, $author, $isbn, $issn) = _parse_biblio_fields($marc_record);
1389 my $dbh = C4::Context->dbh;
1390 # FIXME no controlnumber, originalsource
1391 $isbn = C4::Koha::_isbn_cleanup($isbn); # FIXME C4::Koha::_isbn_cleanup should be made public
1392 my $sth = $dbh->prepare("INSERT INTO import_biblios (import_record_id, title, author, isbn, issn) VALUES (?, ?, ?, ?, ?)");
1393 $sth->execute($import_record_id, $title, $author, $isbn, $issn);
1398 sub _update_biblio_fields {
1399 my ($import_record_id, $marc_record) = @_;
1401 my ($title, $author, $isbn, $issn) = _parse_biblio_fields($marc_record);
1402 my $dbh = C4::Context->dbh;
1403 # FIXME no controlnumber, originalsource
1404 # FIXME 2 - should regularize normalization of ISBN wherever it is done
1408 my $sth = $dbh->prepare("UPDATE import_biblios SET title = ?, author = ?, isbn = ?, issn = ?
1409 WHERE import_record_id = ?");
1410 $sth->execute($title, $author, $isbn, $issn, $import_record_id);
1414 sub _parse_biblio_fields {
1415 my ($marc_record) = @_;
1417 my $dbh = C4::Context->dbh;
1418 my $bibliofields = TransformMarcToKoha($dbh, $marc_record, '');
1419 return ($bibliofields->{'title'}, $bibliofields->{'author'}, $bibliofields->{'isbn'}, $bibliofields->{'issn'});
1423 sub _update_batch_record_counts {
1424 my ($batch_id) = @_;
1426 my $dbh = C4::Context->dbh;
1427 my $sth = $dbh->prepare_cached("UPDATE import_batches SET
1431 WHERE import_batch_id = import_batches.import_batch_id),
1435 JOIN import_items USING (import_record_id)
1436 WHERE import_batch_id = import_batches.import_batch_id
1437 AND record_type = 'biblio')
1438 WHERE import_batch_id = ?");
1439 $sth->bind_param(1, $batch_id);
1444 sub _get_commit_action {
1445 my ($overlay_action, $nomatch_action, $item_action, $overlay_status, $import_record_id, $record_type) = @_;
1447 if ($record_type eq 'biblio') {
1448 my ($bib_result, $bib_match, $item_result);
1450 if ($overlay_status ne 'no_match') {
1451 $bib_match = GetBestRecordMatch($import_record_id);
1452 if ($overlay_action eq 'replace') {
1453 $bib_result = defined($bib_match) ? 'replace' : 'create_new';
1454 } elsif ($overlay_action eq 'create_new') {
1455 $bib_result = 'create_new';
1456 } elsif ($overlay_action eq 'ignore') {
1457 $bib_result = 'ignore';
1459 $item_result = ($item_action eq 'always_add' or $item_action eq 'add_only_for_matches') ? 'create_new' : 'ignore';
1461 $bib_result = $nomatch_action;
1462 $item_result = ($item_action eq 'always_add' or $item_action eq 'add_only_for_new') ? 'create_new' : 'ignore';
1464 return ($bib_result, $item_result, $bib_match);
1465 } else { # must be auths
1466 my ($auth_result, $auth_match);
1468 if ($overlay_status ne 'no_match') {
1469 $auth_match = GetBestRecordMatch($import_record_id);
1470 if ($overlay_action eq 'replace') {
1471 $auth_result = defined($auth_match) ? 'replace' : 'create_new';
1472 } elsif ($overlay_action eq 'create_new') {
1473 $auth_result = 'create_new';
1474 } elsif ($overlay_action eq 'ignore') {
1475 $auth_result = 'ignore';
1478 $auth_result = $nomatch_action;
1481 return ($auth_result, undef, $auth_match);
1486 sub _get_revert_action {
1487 my ($overlay_action, $overlay_status, $status) = @_;
1491 if ($status eq 'ignored') {
1492 $bib_result = 'ignore';
1494 if ($overlay_action eq 'create_new') {
1495 $bib_result = 'delete';
1497 $bib_result = ($overlay_status eq 'match_applied') ? 'restore' : 'delete';
1508 Koha Development Team <http://koha-community.org/>
1510 Galen Charlton <galen.charlton@liblime.com>