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 # Normalize the record so it doesn't have separated diacritics
387 SetUTF8Flag($marc_record);
390 if ($record_type eq 'biblio') {
391 $import_record_id = AddBiblioToBatch($batch_id, $rec_num, $marc_record, $encoding, int(rand(99999)), 0);
393 my @import_items_ids = AddItemsToImportBiblio($batch_id, $import_record_id, $marc_record, 0);
394 $num_items += scalar(@import_items_ids);
396 } elsif ($record_type eq 'auth') {
397 $import_record_id = AddAuthToBatch($batch_id, $rec_num, $marc_record, $encoding, int(rand(99999)), 0, $marc_type);
401 unless ($leave_as_staging) {
402 SetImportBatchStatus($batch_id, 'staged');
404 # FIXME branch_code, number of bibs, number of items
405 _update_batch_record_counts($batch_id);
406 return ($batch_id, $num_valid, $num_items, @invalid_records);
409 =head2 AddItemsToImportBiblio
411 my @import_items_ids = AddItemsToImportBiblio($batch_id,
412 $import_record_id, $marc_record, $update_counts);
416 sub AddItemsToImportBiblio {
417 my $batch_id = shift;
418 my $import_record_id = shift;
419 my $marc_record = shift;
420 my $update_counts = @_ ? shift : 0;
422 my @import_items_ids = ();
424 my $dbh = C4::Context->dbh;
425 my ($item_tag,$item_subfield) = &GetMarcFromKohaField("items.itemnumber",'');
426 foreach my $item_field ($marc_record->field($item_tag)) {
427 my $item_marc = MARC::Record->new();
428 $item_marc->leader("00000 a "); # must set Leader/09 to 'a'
429 $item_marc->append_fields($item_field);
430 $marc_record->delete_field($item_field);
431 my $sth = $dbh->prepare_cached("INSERT INTO import_items (import_record_id, status, marcxml)
433 $sth->bind_param(1, $import_record_id);
434 $sth->bind_param(2, 'staged');
435 $sth->bind_param(3, $item_marc->as_xml());
437 push @import_items_ids, $dbh->{'mysql_insertid'};
441 if ($#import_items_ids > -1) {
442 _update_batch_record_counts($batch_id) if $update_counts;
443 _update_import_record_marc($import_record_id, $marc_record, C4::Context->preference('marcflavour'));
445 return @import_items_ids;
448 =head2 BatchFindDuplicates
450 my $num_with_matches = BatchFindDuplicates($batch_id, $matcher,
451 $max_matches, $progress_interval, $progress_callback);
453 Goes through the records loaded in the batch and attempts to
454 find duplicates for each one. Sets the matching status
455 of each record to "no_match" or "auto_match" as appropriate.
457 The $max_matches parameter is optional; if it is not supplied,
460 The $progress_interval and $progress_callback parameters are
461 optional; if both are supplied, the sub referred to by
462 $progress_callback will be invoked every $progress_interval
463 records using the number of records processed as the
468 sub BatchFindDuplicates {
469 my $batch_id = shift;
471 my $max_matches = @_ ? shift : 10;
473 # optional callback to monitor status
475 my $progress_interval = 0;
476 my $progress_callback = undef;
478 $progress_interval = shift;
479 $progress_callback = shift;
480 $progress_interval = 0 unless $progress_interval =~ /^\d+$/ and $progress_interval > 0;
481 $progress_interval = 0 unless 'CODE' eq ref $progress_callback;
484 my $dbh = C4::Context->dbh;
486 my $sth = $dbh->prepare("SELECT import_record_id, record_type, marc
488 WHERE import_batch_id = ?");
489 $sth->execute($batch_id);
490 my $num_with_matches = 0;
492 while (my $rowref = $sth->fetchrow_hashref) {
494 if ($progress_interval and (0 == ($rec_num % $progress_interval))) {
495 &$progress_callback($rec_num);
497 my $marc_record = MARC::Record->new_from_usmarc($rowref->{'marc'});
499 if (defined $matcher) {
500 @matches = $matcher->get_matches($marc_record, $max_matches);
502 if (scalar(@matches) > 0) {
504 SetImportRecordMatches($rowref->{'import_record_id'}, @matches);
505 SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'auto_match');
507 SetImportRecordMatches($rowref->{'import_record_id'}, ());
508 SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'no_match');
512 return $num_with_matches;
515 =head2 BatchCommitRecords
517 my ($num_added, $num_updated, $num_items_added, $num_items_replaced, $num_items_errored, $num_ignored) =
518 BatchCommitRecords($batch_id, $framework,
519 $progress_interval, $progress_callback);
523 sub BatchCommitRecords {
524 my $batch_id = shift;
525 my $framework = shift;
527 # optional callback to monitor status
529 my $progress_interval = 0;
530 my $progress_callback = undef;
532 $progress_interval = shift;
533 $progress_callback = shift;
534 $progress_interval = 0 unless $progress_interval =~ /^\d+$/ and $progress_interval > 0;
535 $progress_interval = 0 unless 'CODE' eq ref $progress_callback;
541 my $num_items_added = 0;
542 my $num_items_replaced = 0;
543 my $num_items_errored = 0;
545 # commit (i.e., save, all records in the batch)
546 SetImportBatchStatus('importing');
547 my $overlay_action = GetImportBatchOverlayAction($batch_id);
548 my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
549 my $item_action = GetImportBatchItemAction($batch_id);
552 my $dbh = C4::Context->dbh;
553 my $sth = $dbh->prepare("SELECT import_records.import_record_id, record_type, status, overlay_status, marc, encoding
555 LEFT JOIN import_auths ON (import_records.import_record_id=import_auths.import_record_id)
556 LEFT JOIN import_biblios ON (import_records.import_record_id=import_biblios.import_record_id)
557 WHERE import_batch_id = ?");
558 $sth->execute($batch_id);
559 my $marcflavour = C4::Context->preference('marcflavour');
561 while (my $rowref = $sth->fetchrow_hashref) {
562 $record_type = $rowref->{'record_type'};
564 if ($progress_interval and (0 == ($rec_num % $progress_interval))) {
565 &$progress_callback($rec_num);
567 if ($rowref->{'status'} eq 'error' or $rowref->{'status'} eq 'imported') {
573 if ($marcflavour eq 'UNIMARC' && $record_type eq 'auth') {
574 $marc_type = 'UNIMARCAUTH';
575 } elsif ($marcflavour eq 'UNIMARC') {
576 $marc_type = 'UNIMARC';
578 $marc_type = 'USMARC';
580 my $marc_record = MARC::Record->new_from_usmarc($rowref->{'marc'});
582 if ($record_type eq 'biblio') {
583 # remove any item tags - rely on BatchCommitItems
584 ($item_tag,$item_subfield) = &GetMarcFromKohaField("items.itemnumber",'');
585 foreach my $item_field ($marc_record->field($item_tag)) {
586 $marc_record->delete_field($item_field);
590 my ($record_result, $item_result, $record_match) =
591 _get_commit_action($overlay_action, $nomatch_action, $item_action,
592 $rowref->{'overlay_status'}, $rowref->{'import_record_id'}, $record_type);
596 if ($record_result eq 'create_new') {
598 if ($record_type eq 'biblio') {
599 my $biblioitemnumber;
600 ($recordid, $biblioitemnumber) = AddBiblio($marc_record, $framework);
601 $query = "UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?";
602 if ($item_result eq 'create_new' || $item_result eq 'replace') {
603 my ($bib_items_added, $bib_items_replaced, $bib_items_errored) = BatchCommitItems($rowref->{'import_record_id'}, $recordid, $item_result);
604 $num_items_added += $bib_items_added;
605 $num_items_replaced += $bib_items_replaced;
606 $num_items_errored += $bib_items_errored;
609 $recordid = AddAuthority($marc_record, undef, GuessAuthTypeCode($marc_record));
610 $query = "UPDATE import_auths SET matched_authid = ? WHERE import_record_id = ?";
612 my $sth = $dbh->prepare_cached($query);
613 $sth->execute($recordid, $rowref->{'import_record_id'});
615 SetImportRecordStatus($rowref->{'import_record_id'}, 'imported');
616 } elsif ($record_result eq 'replace') {
618 $recordid = $record_match;
620 if ($record_type eq 'biblio') {
621 my ($count, $oldbiblio) = GetBiblio($recordid);
622 $oldxml = GetXmlBiblio($recordid);
624 # remove item fields so that they don't get
625 # added again if record is reverted
626 # 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.
627 my $old_marc = MARC::Record->new_from_xml(StripNonXmlChars($oldxml), 'UTF-8', $rowref->{'encoding'}, $marc_type);
628 foreach my $item_field ($old_marc->field($item_tag)) {
629 $old_marc->delete_field($item_field);
631 $oldxml = $old_marc->as_xml($marc_type);
633 ModBiblio($marc_record, $recordid, $oldbiblio->{'frameworkcode'});
634 $query = "UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?";
636 if ($item_result eq 'create_new' || $item_result eq 'replace') {
637 my ($bib_items_added, $bib_items_replaced, $bib_items_errored) = BatchCommitItems($rowref->{'import_record_id'}, $recordid, $item_result);
638 $num_items_added += $bib_items_added;
639 $num_items_replaced += $bib_items_replaced;
640 $num_items_errored += $bib_items_errored;
643 $oldxml = GetAuthorityXML($recordid);
645 ModAuthority($recordid, $marc_record, GuessAuthTypeCode($marc_record));
646 $query = "UPDATE import_auths SET matched_authid = ? WHERE import_record_id = ?";
648 my $sth = $dbh->prepare_cached("UPDATE import_records SET marcxml_old = ? WHERE import_record_id = ?");
649 $sth->execute($oldxml, $rowref->{'import_record_id'});
651 my $sth2 = $dbh->prepare_cached($query);
652 $sth2->execute($recordid, $rowref->{'import_record_id'});
654 SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'match_applied');
655 SetImportRecordStatus($rowref->{'import_record_id'}, 'imported');
656 } elsif ($record_result eq 'ignore') {
658 $recordid = $record_match;
659 if ($record_type eq 'biblio' and defined $recordid and $item_result eq 'create_new') {
660 my ($bib_items_added, $bib_items_replaced, $bib_items_errored) = BatchCommitItems($rowref->{'import_record_id'}, $recordid, $item_result);
661 $num_items_added += $bib_items_added;
662 $num_items_replaced += $bib_items_replaced;
663 $num_items_errored += $bib_items_errored;
664 # still need to record the matched biblionumber so that the
665 # items can be reverted
666 my $sth2 = $dbh->prepare_cached("UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?");
667 $sth2->execute($recordid, $rowref->{'import_record_id'});
668 SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'match_applied');
670 SetImportRecordStatus($rowref->{'import_record_id'}, 'ignored');
674 SetImportBatchStatus($batch_id, 'imported');
675 return ($num_added, $num_updated, $num_items_added, $num_items_replaced, $num_items_errored, $num_ignored);
678 =head2 BatchCommitItems
680 ($num_items_added, $num_items_errored) =
681 BatchCommitItems($import_record_id, $biblionumber);
685 sub BatchCommitItems {
686 my ($import_record_id, $biblionumber, $action) = @_;
688 my $dbh = C4::Context->dbh;
690 my ($num_items_added, $num_items_errored, $num_items_replaced) = 0;
691 my $sth = $dbh->prepare("SELECT import_items_id, import_items.marcxml, encoding
693 JOIN import_records USING (import_record_id)
694 WHERE import_record_id = ?
695 ORDER BY import_items_id");
696 $sth->bind_param(1, $import_record_id);
698 while (my $row = $sth->fetchrow_hashref()) {
699 my $item_marc = MARC::Record->new_from_xml(StripNonXmlChars($row->{'marcxml'}), 'UTF-8', $row->{'encoding'});
700 #delete date_due subfield as to not accidentally delete item checkout due dates
701 my ($MARCfield,$MARCsubfield) = GetMarcFromKohaField('items.onloan', GetFrameworkCode($biblionumber));
702 $item_marc->field($MARCfield)->delete_subfield(code => $MARCsubfield);
703 # FIXME - duplicate barcode check needs to become part of AddItemFromMarc()
704 my $item = TransformMarcToKoha($dbh, $item_marc);
705 my $duplicate_barcode = exists($item->{'barcode'}) && GetItemnumberFromBarcode($item->{'barcode'});
706 my $duplicate_itemnumber = exists($item->{'itemnumber'});
707 my $updsth = $dbh->prepare("UPDATE import_items SET status = ?, itemnumber = ? WHERE import_items_id = ?");
708 if($action eq "replace" && $duplicate_itemnumber){
709 ModItemFromMarc($item_marc, $biblionumber, $item->{itemnumber});
710 $updsth->bind_param(1, 'imported');
711 $updsth->bind_param(2, $item->{itemnumber});
712 $updsth->bind_param(3, $row->{'import_items_id'});
715 $num_items_replaced++;
717 elsif ($duplicate_barcode) {
718 $updsth->bind_param(1, 'error');
719 $updsth->bind_param(2, 'duplicate item barcode');
720 $updsth->bind_param(3, $row->{'import_items_id'});
722 $num_items_errored++;
724 my ($item_biblionumber, $biblioitemnumber, $itemnumber) = AddItemFromMarc($item_marc, $biblionumber);
725 $updsth->bind_param(1, 'imported');
726 $updsth->bind_param(2, $itemnumber);
727 $updsth->bind_param(3, $row->{'import_items_id'});
734 return ($num_items_added, $num_items_replaced, $num_items_errored);
737 =head2 BatchRevertRecords
739 my ($num_deleted, $num_errors, $num_reverted, $num_items_deleted,
740 $num_ignored) = BatchRevertRecords($batch_id);
744 sub BatchRevertRecords {
745 my $batch_id = shift;
750 my $num_reverted = 0;
752 my $num_items_deleted = 0;
753 # commit (i.e., save, all records in the batch)
754 SetImportBatchStatus('reverting');
755 my $overlay_action = GetImportBatchOverlayAction($batch_id);
756 my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
757 my $dbh = C4::Context->dbh;
758 my $sth = $dbh->prepare("SELECT import_records.import_record_id, record_type, status, overlay_status, marcxml_old, encoding, matched_biblionumber, matched_authid
760 LEFT JOIN import_auths ON (import_records.import_record_id=import_auths.import_record_id)
761 LEFT JOIN import_biblios ON (import_records.import_record_id=import_biblios.import_record_id)
762 WHERE import_batch_id = ?");
763 $sth->execute($batch_id);
765 my $marcflavour = C4::Context->preference('marcflavour');
766 while (my $rowref = $sth->fetchrow_hashref) {
767 $record_type = $rowref->{'record_type'};
768 if ($rowref->{'status'} eq 'error' or $rowref->{'status'} eq 'reverted') {
772 if ($marcflavour eq 'UNIMARC' && $record_type eq 'auth') {
773 $marc_type = 'UNIMARCAUTH';
774 } elsif ($marcflavour eq 'UNIMARC') {
775 $marc_type = 'UNIMARC';
777 $marc_type = 'USMARC';
780 my $record_result = _get_revert_action($overlay_action, $rowref->{'overlay_status'}, $rowref->{'status'});
782 if ($record_result eq 'delete') {
784 if ($record_type eq 'biblio') {
785 $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
786 $error = DelBiblio($rowref->{'matched_biblionumber'});
788 my $deletedauthid = DelAuthority($rowref->{'matched_authid'});
790 if (defined $error) {
794 SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
796 } elsif ($record_result eq 'restore') {
798 my $old_record = MARC::Record->new_from_xml(StripNonXmlChars($rowref->{'marcxml_old'}), 'UTF-8', $rowref->{'encoding'}, $marc_type);
799 if ($record_type eq 'biblio') {
800 my $biblionumber = $rowref->{'matched_biblionumber'};
801 my ($count, $oldbiblio) = GetBiblio($biblionumber);
802 $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
803 ModBiblio($old_record, $biblionumber, $oldbiblio->{'frameworkcode'});
805 my $authid = $rowref->{'matched_authid'};
806 ModAuthority($authid, $old_record, GuessAuthTypeCode($old_record));
808 SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
809 } elsif ($record_result eq 'ignore') {
810 if ($record_type eq 'biblio') {
811 $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
813 SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
816 if ($record_type eq 'biblio') {
817 # remove matched_biblionumber only if there is no 'imported' item left
818 $query = "UPDATE import_biblios SET matched_biblionumber = NULL WHERE import_record_id = ?";
819 $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')";
821 $query = "UPDATE import_auths SET matched_authid = NULL WHERE import_record_id = ?";
823 my $sth2 = $dbh->prepare_cached($query);
824 $sth2->execute($rowref->{'import_record_id'});
828 SetImportBatchStatus($batch_id, 'reverted');
829 return ($num_deleted, $num_errors, $num_reverted, $num_items_deleted, $num_ignored);
832 =head2 BatchRevertItems
834 my $num_items_deleted = BatchRevertItems($import_record_id, $biblionumber);
838 sub BatchRevertItems {
839 my ($import_record_id, $biblionumber) = @_;
841 my $dbh = C4::Context->dbh;
842 my $num_items_deleted = 0;
844 my $sth = $dbh->prepare_cached("SELECT import_items_id, itemnumber
846 JOIN items USING (itemnumber)
847 WHERE import_record_id = ?");
848 $sth->bind_param(1, $import_record_id);
850 while (my $row = $sth->fetchrow_hashref()) {
851 my $error = DelItemCheck($dbh, $biblionumber, $row->{'itemnumber'});
853 my $updsth = $dbh->prepare("UPDATE import_items SET status = ? WHERE import_items_id = ?");
854 $updsth->bind_param(1, 'reverted');
855 $updsth->bind_param(2, $row->{'import_items_id'});
858 $num_items_deleted++;
865 return $num_items_deleted;
870 CleanBatch($batch_id)
872 Deletes all staged records from the import batch
873 and sets the status of the batch to 'cleaned'. Note
874 that deleting a stage record does *not* affect
875 any record that has been committed to the database.
880 my $batch_id = shift;
881 return unless defined $batch_id;
883 C4::Context->dbh->do('DELETE FROM import_records WHERE import_batch_id = ?', {}, $batch_id);
884 SetImportBatchStatus($batch_id, 'cleaned');
887 =head2 GetAllImportBatches
889 my $results = GetAllImportBatches();
891 Returns a references to an array of hash references corresponding
892 to all import_batches rows (of batch_type 'batch'), sorted in
893 ascending order by import_batch_id.
897 sub GetAllImportBatches {
898 my $dbh = C4::Context->dbh;
899 my $sth = $dbh->prepare_cached("SELECT * FROM import_batches
900 WHERE batch_type IN ('batch', 'webservice')
901 ORDER BY import_batch_id ASC");
905 while (my $row = $sth->fetchrow_hashref) {
906 push @$results, $row;
912 =head2 GetStagedWebserviceBatches
914 my $batch_ids = GetStagedWebserviceBatches();
916 Returns a references to an array of batch id's
917 of batch_type 'webservice' that are not imported
921 my $PENDING_WEBSERVICE_BATCHES_QRY = <<EOQ;
922 SELECT import_batch_id FROM import_batches
923 WHERE batch_type = 'webservice'
924 AND import_status = 'staged'
926 sub GetStagedWebserviceBatches {
927 my $dbh = C4::Context->dbh;
928 return $dbh->selectcol_arrayref($PENDING_WEBSERVICE_BATCHES_QRY);
931 =head2 GetImportBatchRangeDesc
933 my $results = GetImportBatchRangeDesc($offset, $results_per_group);
935 Returns a reference to an array of hash references corresponding to
936 import_batches rows (sorted in descending order by import_batch_id)
937 start at the given offset.
941 sub GetImportBatchRangeDesc {
942 my ($offset, $results_per_group) = @_;
944 my $dbh = C4::Context->dbh;
945 my $query = "SELECT * FROM import_batches
946 WHERE batch_type IN ('batch', 'webservice')
947 ORDER BY import_batch_id DESC";
949 if ($results_per_group){
950 $query .= " LIMIT ?";
951 push(@params, $results_per_group);
954 $query .= " OFFSET ?";
955 push(@params, $offset);
957 my $sth = $dbh->prepare_cached($query);
958 $sth->execute(@params);
959 my $results = $sth->fetchall_arrayref({});
964 =head2 GetItemNumbersFromImportBatch
966 my @itemsnos = GetItemNumbersFromImportBatch($batch_id);
970 sub GetItemNumbersFromImportBatch {
972 my $dbh = C4::Context->dbh;
973 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=?");
974 $sth->execute($batch_id);
976 while ( my ($itm) = $sth->fetchrow_array ) {
982 =head2 GetNumberOfImportBatches
984 my $count = GetNumberOfImportBatches();
988 sub GetNumberOfNonZ3950ImportBatches {
989 my $dbh = C4::Context->dbh;
990 my $sth = $dbh->prepare("SELECT COUNT(*) FROM import_batches WHERE batch_type != 'z3950'");
992 my ($count) = $sth->fetchrow_array();
997 =head2 GetImportRecordsRange
999 my $results = GetImportRecordsRange($batch_id, $offset, $results_per_group);
1001 Returns a reference to an array of hash references corresponding to
1002 import_biblios/import_auths/import_records rows for a given batch
1003 starting at the given offset.
1007 sub GetImportRecordsRange {
1008 my ($batch_id, $offset, $results_per_group, $status) = @_;
1010 my $dbh = C4::Context->dbh;
1011 my $query = "SELECT title, author, isbn, issn, authorized_heading, import_records.import_record_id,
1012 record_sequence, status, overlay_status,
1013 matched_biblionumber, matched_authid, record_type
1015 LEFT JOIN import_auths ON (import_records.import_record_id=import_auths.import_record_id)
1016 LEFT JOIN import_biblios ON (import_records.import_record_id=import_biblios.import_record_id)
1017 WHERE import_batch_id = ?";
1019 push(@params, $batch_id);
1021 $query .= " AND status=?";
1022 push(@params,$status);
1024 $query.=" ORDER BY import_record_id";
1026 if($results_per_group){
1027 $query .= " LIMIT ?";
1028 push(@params, $results_per_group);
1031 $query .= " OFFSET ?";
1032 push(@params, $offset);
1034 my $sth = $dbh->prepare_cached($query);
1035 $sth->execute(@params);
1036 my $results = $sth->fetchall_arrayref({});
1042 =head2 GetBestRecordMatch
1044 my $record_id = GetBestRecordMatch($import_record_id);
1048 sub GetBestRecordMatch {
1049 my ($import_record_id) = @_;
1051 my $dbh = C4::Context->dbh;
1052 my $sth = $dbh->prepare("SELECT candidate_match_id
1053 FROM import_record_matches
1054 JOIN import_records ON ( import_record_matches.import_record_id = import_records.import_record_id )
1055 LEFT JOIN biblio ON ( candidate_match_id = biblio.biblionumber )
1056 LEFT JOIN auth_header ON ( candidate_match_id = auth_header.authid )
1057 WHERE import_record_matches.import_record_id = ? AND
1058 ( (import_records.record_type = 'biblio' AND biblio.biblionumber IS NOT NULL) OR
1059 (import_records.record_type = 'auth' AND auth_header.authid IS NOT NULL) )
1060 ORDER BY score DESC, candidate_match_id DESC");
1061 $sth->execute($import_record_id);
1062 my ($record_id) = $sth->fetchrow_array();
1067 =head2 GetImportBatchStatus
1069 my $status = GetImportBatchStatus($batch_id);
1073 sub GetImportBatchStatus {
1074 my ($batch_id) = @_;
1076 my $dbh = C4::Context->dbh;
1077 my $sth = $dbh->prepare("SELECT import_status FROM import_batches WHERE import_batch_id = ?");
1078 $sth->execute($batch_id);
1079 my ($status) = $sth->fetchrow_array();
1085 =head2 SetImportBatchStatus
1087 SetImportBatchStatus($batch_id, $new_status);
1091 sub SetImportBatchStatus {
1092 my ($batch_id, $new_status) = @_;
1094 my $dbh = C4::Context->dbh;
1095 my $sth = $dbh->prepare("UPDATE import_batches SET import_status = ? WHERE import_batch_id = ?");
1096 $sth->execute($new_status, $batch_id);
1101 =head2 GetImportBatchOverlayAction
1103 my $overlay_action = GetImportBatchOverlayAction($batch_id);
1107 sub GetImportBatchOverlayAction {
1108 my ($batch_id) = @_;
1110 my $dbh = C4::Context->dbh;
1111 my $sth = $dbh->prepare("SELECT overlay_action FROM import_batches WHERE import_batch_id = ?");
1112 $sth->execute($batch_id);
1113 my ($overlay_action) = $sth->fetchrow_array();
1115 return $overlay_action;
1120 =head2 SetImportBatchOverlayAction
1122 SetImportBatchOverlayAction($batch_id, $new_overlay_action);
1126 sub SetImportBatchOverlayAction {
1127 my ($batch_id, $new_overlay_action) = @_;
1129 my $dbh = C4::Context->dbh;
1130 my $sth = $dbh->prepare("UPDATE import_batches SET overlay_action = ? WHERE import_batch_id = ?");
1131 $sth->execute($new_overlay_action, $batch_id);
1136 =head2 GetImportBatchNoMatchAction
1138 my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
1142 sub GetImportBatchNoMatchAction {
1143 my ($batch_id) = @_;
1145 my $dbh = C4::Context->dbh;
1146 my $sth = $dbh->prepare("SELECT nomatch_action FROM import_batches WHERE import_batch_id = ?");
1147 $sth->execute($batch_id);
1148 my ($nomatch_action) = $sth->fetchrow_array();
1150 return $nomatch_action;
1155 =head2 SetImportBatchNoMatchAction
1157 SetImportBatchNoMatchAction($batch_id, $new_nomatch_action);
1161 sub SetImportBatchNoMatchAction {
1162 my ($batch_id, $new_nomatch_action) = @_;
1164 my $dbh = C4::Context->dbh;
1165 my $sth = $dbh->prepare("UPDATE import_batches SET nomatch_action = ? WHERE import_batch_id = ?");
1166 $sth->execute($new_nomatch_action, $batch_id);
1171 =head2 GetImportBatchItemAction
1173 my $item_action = GetImportBatchItemAction($batch_id);
1177 sub GetImportBatchItemAction {
1178 my ($batch_id) = @_;
1180 my $dbh = C4::Context->dbh;
1181 my $sth = $dbh->prepare("SELECT item_action FROM import_batches WHERE import_batch_id = ?");
1182 $sth->execute($batch_id);
1183 my ($item_action) = $sth->fetchrow_array();
1185 return $item_action;
1190 =head2 SetImportBatchItemAction
1192 SetImportBatchItemAction($batch_id, $new_item_action);
1196 sub SetImportBatchItemAction {
1197 my ($batch_id, $new_item_action) = @_;
1199 my $dbh = C4::Context->dbh;
1200 my $sth = $dbh->prepare("UPDATE import_batches SET item_action = ? WHERE import_batch_id = ?");
1201 $sth->execute($new_item_action, $batch_id);
1206 =head2 GetImportBatchMatcher
1208 my $matcher_id = GetImportBatchMatcher($batch_id);
1212 sub GetImportBatchMatcher {
1213 my ($batch_id) = @_;
1215 my $dbh = C4::Context->dbh;
1216 my $sth = $dbh->prepare("SELECT matcher_id FROM import_batches WHERE import_batch_id = ?");
1217 $sth->execute($batch_id);
1218 my ($matcher_id) = $sth->fetchrow_array();
1225 =head2 SetImportBatchMatcher
1227 SetImportBatchMatcher($batch_id, $new_matcher_id);
1231 sub SetImportBatchMatcher {
1232 my ($batch_id, $new_matcher_id) = @_;
1234 my $dbh = C4::Context->dbh;
1235 my $sth = $dbh->prepare("UPDATE import_batches SET matcher_id = ? WHERE import_batch_id = ?");
1236 $sth->execute($new_matcher_id, $batch_id);
1241 =head2 GetImportRecordOverlayStatus
1243 my $overlay_status = GetImportRecordOverlayStatus($import_record_id);
1247 sub GetImportRecordOverlayStatus {
1248 my ($import_record_id) = @_;
1250 my $dbh = C4::Context->dbh;
1251 my $sth = $dbh->prepare("SELECT overlay_status FROM import_records WHERE import_record_id = ?");
1252 $sth->execute($import_record_id);
1253 my ($overlay_status) = $sth->fetchrow_array();
1255 return $overlay_status;
1260 =head2 SetImportRecordOverlayStatus
1262 SetImportRecordOverlayStatus($import_record_id, $new_overlay_status);
1266 sub SetImportRecordOverlayStatus {
1267 my ($import_record_id, $new_overlay_status) = @_;
1269 my $dbh = C4::Context->dbh;
1270 my $sth = $dbh->prepare("UPDATE import_records SET overlay_status = ? WHERE import_record_id = ?");
1271 $sth->execute($new_overlay_status, $import_record_id);
1276 =head2 GetImportRecordStatus
1278 my $overlay_status = GetImportRecordStatus($import_record_id);
1282 sub GetImportRecordStatus {
1283 my ($import_record_id) = @_;
1285 my $dbh = C4::Context->dbh;
1286 my $sth = $dbh->prepare("SELECT status FROM import_records WHERE import_record_id = ?");
1287 $sth->execute($import_record_id);
1288 my ($overlay_status) = $sth->fetchrow_array();
1290 return $overlay_status;
1295 =head2 SetImportRecordStatus
1297 SetImportRecordStatus($import_record_id, $new_overlay_status);
1301 sub SetImportRecordStatus {
1302 my ($import_record_id, $new_overlay_status) = @_;
1304 my $dbh = C4::Context->dbh;
1305 my $sth = $dbh->prepare("UPDATE import_records SET status = ? WHERE import_record_id = ?");
1306 $sth->execute($new_overlay_status, $import_record_id);
1311 =head2 GetImportRecordMatches
1313 my $results = GetImportRecordMatches($import_record_id, $best_only);
1317 sub GetImportRecordMatches {
1318 my $import_record_id = shift;
1319 my $best_only = @_ ? shift : 0;
1321 my $dbh = C4::Context->dbh;
1322 # FIXME currently biblio only
1323 my $sth = $dbh->prepare_cached("SELECT title, author, biblionumber,
1324 candidate_match_id, score, record_type
1326 JOIN import_record_matches USING (import_record_id)
1327 LEFT JOIN biblio ON (biblionumber = candidate_match_id)
1328 WHERE import_record_id = ?
1329 ORDER BY score DESC, biblionumber DESC");
1330 $sth->bind_param(1, $import_record_id);
1333 while (my $row = $sth->fetchrow_hashref) {
1334 if ($row->{'record_type'} eq 'auth') {
1335 $row->{'authorized_heading'} = C4::AuthoritiesMarc::GetAuthorizedHeading( { authid => $row->{'candidate_match_id'} } );
1337 next if ($row->{'record_type'} eq 'biblio' && not $row->{'biblionumber'});
1338 push @$results, $row;
1348 =head2 SetImportRecordMatches
1350 SetImportRecordMatches($import_record_id, @matches);
1354 sub SetImportRecordMatches {
1355 my $import_record_id = shift;
1358 my $dbh = C4::Context->dbh;
1359 my $delsth = $dbh->prepare("DELETE FROM import_record_matches WHERE import_record_id = ?");
1360 $delsth->execute($import_record_id);
1363 my $sth = $dbh->prepare("INSERT INTO import_record_matches (import_record_id, candidate_match_id, score)
1365 foreach my $match (@matches) {
1366 $sth->execute($import_record_id, $match->{'record_id'}, $match->{'score'});
1371 # internal functions
1373 sub _create_import_record {
1374 my ($batch_id, $record_sequence, $marc_record, $record_type, $encoding, $z3950random, $marc_type) = @_;
1376 my $dbh = C4::Context->dbh;
1377 my $sth = $dbh->prepare("INSERT INTO import_records (import_batch_id, record_sequence, marc, marcxml,
1378 record_type, encoding, z3950random)
1379 VALUES (?, ?, ?, ?, ?, ?, ?)");
1380 $sth->execute($batch_id, $record_sequence, $marc_record->as_usmarc(), $marc_record->as_xml($marc_type),
1381 $record_type, $encoding, $z3950random);
1382 my $import_record_id = $dbh->{'mysql_insertid'};
1384 return $import_record_id;
1387 sub _update_import_record_marc {
1388 my ($import_record_id, $marc_record, $marc_type) = @_;
1390 my $dbh = C4::Context->dbh;
1391 my $sth = $dbh->prepare("UPDATE import_records SET marc = ?, marcxml = ?
1392 WHERE import_record_id = ?");
1393 $sth->execute($marc_record->as_usmarc(), $marc_record->as_xml($marc_type), $import_record_id);
1397 sub _add_auth_fields {
1398 my ($import_record_id, $marc_record) = @_;
1401 if ($marc_record->field('001')) {
1402 $controlnumber = $marc_record->field('001')->data();
1404 my $authorized_heading = C4::AuthoritiesMarc::GetAuthorizedHeading({ record => $marc_record });
1405 my $dbh = C4::Context->dbh;
1406 my $sth = $dbh->prepare("INSERT INTO import_auths (import_record_id, control_number, authorized_heading) VALUES (?, ?, ?)");
1407 $sth->execute($import_record_id, $controlnumber, $authorized_heading);
1411 sub _add_biblio_fields {
1412 my ($import_record_id, $marc_record) = @_;
1414 my ($title, $author, $isbn, $issn) = _parse_biblio_fields($marc_record);
1415 my $dbh = C4::Context->dbh;
1416 # FIXME no controlnumber, originalsource
1417 $isbn = C4::Koha::_isbn_cleanup($isbn); # FIXME C4::Koha::_isbn_cleanup should be made public
1418 my $sth = $dbh->prepare("INSERT INTO import_biblios (import_record_id, title, author, isbn, issn) VALUES (?, ?, ?, ?, ?)");
1419 $sth->execute($import_record_id, $title, $author, $isbn, $issn);
1424 sub _update_biblio_fields {
1425 my ($import_record_id, $marc_record) = @_;
1427 my ($title, $author, $isbn, $issn) = _parse_biblio_fields($marc_record);
1428 my $dbh = C4::Context->dbh;
1429 # FIXME no controlnumber, originalsource
1430 # FIXME 2 - should regularize normalization of ISBN wherever it is done
1434 my $sth = $dbh->prepare("UPDATE import_biblios SET title = ?, author = ?, isbn = ?, issn = ?
1435 WHERE import_record_id = ?");
1436 $sth->execute($title, $author, $isbn, $issn, $import_record_id);
1440 sub _parse_biblio_fields {
1441 my ($marc_record) = @_;
1443 my $dbh = C4::Context->dbh;
1444 my $bibliofields = TransformMarcToKoha($dbh, $marc_record, '');
1445 return ($bibliofields->{'title'}, $bibliofields->{'author'}, $bibliofields->{'isbn'}, $bibliofields->{'issn'});
1449 sub _update_batch_record_counts {
1450 my ($batch_id) = @_;
1452 my $dbh = C4::Context->dbh;
1453 my $sth = $dbh->prepare_cached("UPDATE import_batches SET
1457 WHERE import_batch_id = import_batches.import_batch_id),
1461 JOIN import_items USING (import_record_id)
1462 WHERE import_batch_id = import_batches.import_batch_id
1463 AND record_type = 'biblio')
1464 WHERE import_batch_id = ?");
1465 $sth->bind_param(1, $batch_id);
1470 sub _get_commit_action {
1471 my ($overlay_action, $nomatch_action, $item_action, $overlay_status, $import_record_id, $record_type) = @_;
1473 if ($record_type eq 'biblio') {
1474 my ($bib_result, $bib_match, $item_result);
1476 if ($overlay_status ne 'no_match') {
1477 $bib_match = GetBestRecordMatch($import_record_id);
1478 if ($overlay_action eq 'replace') {
1479 $bib_result = defined($bib_match) ? 'replace' : 'create_new';
1480 } elsif ($overlay_action eq 'create_new') {
1481 $bib_result = 'create_new';
1482 } elsif ($overlay_action eq 'ignore') {
1483 $bib_result = 'ignore';
1485 if($item_action eq 'always_add' or $item_action eq 'add_only_for_matches'){
1486 $item_result = 'create_new';
1488 elsif($item_action eq 'replace'){
1489 $item_result = 'replace';
1492 $item_result = 'ignore';
1495 $bib_result = $nomatch_action;
1496 $item_result = ($item_action eq 'always_add' or $item_action eq 'add_only_for_new') ? 'create_new' : 'ignore';
1498 return ($bib_result, $item_result, $bib_match);
1499 } else { # must be auths
1500 my ($auth_result, $auth_match);
1502 if ($overlay_status ne 'no_match') {
1503 $auth_match = GetBestRecordMatch($import_record_id);
1504 if ($overlay_action eq 'replace') {
1505 $auth_result = defined($auth_match) ? 'replace' : 'create_new';
1506 } elsif ($overlay_action eq 'create_new') {
1507 $auth_result = 'create_new';
1508 } elsif ($overlay_action eq 'ignore') {
1509 $auth_result = 'ignore';
1512 $auth_result = $nomatch_action;
1515 return ($auth_result, undef, $auth_match);
1520 sub _get_revert_action {
1521 my ($overlay_action, $overlay_status, $status) = @_;
1525 if ($status eq 'ignored') {
1526 $bib_result = 'ignore';
1528 if ($overlay_action eq 'create_new') {
1529 $bib_result = 'delete';
1531 $bib_result = ($overlay_status eq 'match_applied') ? 'restore' : 'delete';
1542 Koha Development Team <http://koha-community.org/>
1544 Galen Charlton <galen.charlton@liblime.com>