MT 2037 : Update ISBD
[koha.git] / C4 / ImportBatch.pm
blob8f259a33f73a065c37c87ebaf69bcfa6ec5e507d
1 package C4::ImportBatch;
3 # Copyright (C) 2007 LibLime
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
10 # version.
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 with
17 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
18 # Suite 330, Boston, MA 02111-1307 USA
20 use strict;
21 use warnings;
23 use C4::Context;
24 use C4::Koha;
25 use C4::Biblio;
26 use C4::Items;
27 use C4::Charset;
29 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
31 BEGIN {
32 # set the version for version checking
33 $VERSION = 3.01;
34 require Exporter;
35 @ISA = qw(Exporter);
36 @EXPORT = qw(
37 GetZ3950BatchId
38 GetImportRecordMarc
39 AddImportBatch
40 GetImportBatch
41 AddBiblioToBatch
42 ModBiblioInBatch
44 BatchStageMarcRecords
45 BatchFindBibDuplicates
46 BatchCommitBibRecords
47 BatchRevertBibRecords
48 CleanBatch
50 GetAllImportBatches
51 GetImportBatchRangeDesc
52 GetNumberOfNonZ3950ImportBatches
53 GetImportBibliosRange
54 GetItemNumbersFromImportBatch
56 GetImportBatchStatus
57 SetImportBatchStatus
58 GetImportBatchOverlayAction
59 SetImportBatchOverlayAction
60 GetImportBatchNoMatchAction
61 SetImportBatchNoMatchAction
62 GetImportBatchItemAction
63 SetImportBatchItemAction
64 GetImportBatchMatcher
65 SetImportBatchMatcher
66 GetImportRecordOverlayStatus
67 SetImportRecordOverlayStatus
68 GetImportRecordStatus
69 SetImportRecordStatus
70 GetImportRecordMatches
71 SetImportRecordMatches
75 =head1 NAME
77 C4::ImportBatch - manage batches of imported MARC records
79 =head1 SYNOPSIS
81 =over 4
83 use C4::ImportBatch;
85 =back
87 =head1 FUNCTIONS
89 =head2 GetZ3950BatchId
91 =over 4
93 my $batchid = GetZ3950BatchId($z3950server);
95 =back
97 Retrieves the ID of the import batch for the Z39.50
98 reservoir for the given target. If necessary,
99 creates the import batch.
101 =cut
103 sub GetZ3950BatchId {
104 my ($z3950server) = @_;
106 my $dbh = C4::Context->dbh;
107 my $sth = $dbh->prepare("SELECT import_batch_id FROM import_batches
108 WHERE batch_type = 'z3950'
109 AND file_name = ?");
110 $sth->execute($z3950server);
111 my $rowref = $sth->fetchrow_arrayref();
112 $sth->finish();
113 if (defined $rowref) {
114 return $rowref->[0];
115 } else {
116 my $batch_id = AddImportBatch('create_new', 'staged', 'z3950', $z3950server, '');
117 return $batch_id;
122 =head2 GetImportRecordMarc
124 =over 4
126 my ($marcblob, $encoding) = GetImportRecordMarc($import_record_id);
128 =back
130 =cut
132 sub GetImportRecordMarc {
133 my ($import_record_id) = @_;
135 my $dbh = C4::Context->dbh;
136 my $sth = $dbh->prepare("SELECT marc, encoding FROM import_records WHERE import_record_id = ?");
137 $sth->execute($import_record_id);
138 my ($marc, $encoding) = $sth->fetchrow();
139 $sth->finish();
140 return $marc, $encoding;
144 =head2 AddImportBatch
146 =over 4
148 my $batch_id = AddImportBatch($overlay_action, $import_status, $type, $file_name, $comments);
150 =back
152 =cut
154 sub AddImportBatch {
155 my ($overlay_action, $import_status, $type, $file_name, $comments) = @_;
157 my $dbh = C4::Context->dbh;
158 my $sth = $dbh->prepare("INSERT INTO import_batches (overlay_action, import_status, batch_type,
159 file_name, comments)
160 VALUES (?, ?, ?, ?, ?)");
161 $sth->execute($overlay_action, $import_status, $type, $file_name, $comments);
162 my $batch_id = $dbh->{'mysql_insertid'};
163 $sth->finish();
165 return $batch_id;
169 =head2 GetImportBatch
171 =over 4
173 my $row = GetImportBatch($batch_id);
175 =back
177 Retrieve a hashref of an import_batches row.
179 =cut
181 sub GetImportBatch {
182 my ($batch_id) = @_;
184 my $dbh = C4::Context->dbh;
185 my $sth = $dbh->prepare_cached("SELECT * FROM import_batches WHERE import_batch_id = ?");
186 $sth->bind_param(1, $batch_id);
187 $sth->execute();
188 my $result = $sth->fetchrow_hashref;
189 $sth->finish();
190 return $result;
194 =head2 AddBiblioToBatch
196 =over 4
198 my $import_record_id = AddBiblioToBatch($batch_id, $record_sequence, $marc_record, $encoding, $z3950random, $update_counts);
200 =back
202 =cut
204 sub AddBiblioToBatch {
205 my $batch_id = shift;
206 my $record_sequence = shift;
207 my $marc_record = shift;
208 my $encoding = shift;
209 my $z3950random = shift;
210 my $update_counts = @_ ? shift : 1;
212 my $import_record_id = _create_import_record($batch_id, $record_sequence, $marc_record, 'biblio', $encoding, $z3950random);
213 _add_biblio_fields($import_record_id, $marc_record);
214 _update_batch_record_counts($batch_id) if $update_counts;
215 return $import_record_id;
218 =head2 ModBiblioInBatch
220 =over 4
222 ModBiblioInBatch($import_record_id, $marc_record);
224 =back
226 =cut
228 sub ModBiblioInBatch {
229 my ($import_record_id, $marc_record) = @_;
231 _update_import_record_marc($import_record_id, $marc_record);
232 _update_biblio_fields($import_record_id, $marc_record);
236 =head2 BatchStageMarcRecords
238 =over 4
240 ($batch_id, $num_records, $num_items, @invalid_records) =
241 BatchStageMarcRecords($marc_flavor, $marc_records, $file_name,
242 $comments, $branch_code, $parse_items,
243 $leave_as_staging,
244 $progress_interval, $progress_callback);
246 =back
248 =cut
250 sub BatchStageMarcRecords {
251 my $marc_flavor = shift;
252 my $marc_records = shift;
253 my $file_name = shift;
254 my $comments = shift;
255 my $branch_code = shift;
256 my $parse_items = shift;
257 my $leave_as_staging = shift;
259 # optional callback to monitor status
260 # of job
261 my $progress_interval = 0;
262 my $progress_callback = undef;
263 if ($#_ == 1) {
264 $progress_interval = shift;
265 $progress_callback = shift;
266 $progress_interval = 0 unless $progress_interval =~ /^\d+$/ and $progress_interval > 0;
267 $progress_interval = 0 unless 'CODE' eq ref $progress_callback;
270 my $batch_id = AddImportBatch('create_new', 'staging', 'batch', $file_name, $comments);
271 if ($parse_items) {
272 SetImportBatchItemAction($batch_id, 'always_add');
273 } else {
274 SetImportBatchItemAction($batch_id, 'ignore');
277 my @invalid_records = ();
278 my $num_valid = 0;
279 my $num_items = 0;
280 # FIXME - for now, we're dealing only with bibs
281 my $rec_num = 0;
282 foreach my $marc_blob (split(/\x1D/, $marc_records)) {
283 $marc_blob =~ s/^\s+//g;
284 $marc_blob =~ s/\s+$//g;
285 next unless $marc_blob;
286 $rec_num++;
287 if ($progress_interval and (0 == ($rec_num % $progress_interval))) {
288 &$progress_callback($rec_num);
290 my ($marc_record, $charset_guessed, $char_errors) =
291 MarcToUTF8Record($marc_blob, C4::Context->preference("marcflavour"));
292 my $import_record_id;
293 if (scalar($marc_record->fields()) == 0) {
294 push @invalid_records, $marc_blob;
295 } else {
296 $num_valid++;
297 $import_record_id = AddBiblioToBatch($batch_id, $rec_num, $marc_record, $marc_flavor, int(rand(99999)), 0);
298 if ($parse_items) {
299 my @import_items_ids = AddItemsToImportBiblio($batch_id, $import_record_id, $marc_record, 0);
300 $num_items += scalar(@import_items_ids);
304 unless ($leave_as_staging) {
305 SetImportBatchStatus($batch_id, 'staged');
307 # FIXME branch_code, number of bibs, number of items
308 _update_batch_record_counts($batch_id);
309 return ($batch_id, $num_valid, $num_items, @invalid_records);
312 =head2 AddItemsToImportBiblio
314 =over 4
316 my @import_items_ids = AddItemsToImportBiblio($batch_id, $import_record_id, $marc_record, $update_counts);
318 =back
320 =cut
322 sub AddItemsToImportBiblio {
323 my $batch_id = shift;
324 my $import_record_id = shift;
325 my $marc_record = shift;
326 my $update_counts = @_ ? shift : 0;
328 my @import_items_ids = ();
330 my $dbh = C4::Context->dbh;
331 my ($item_tag,$item_subfield) = &GetMarcFromKohaField("items.itemnumber",'');
332 foreach my $item_field ($marc_record->field($item_tag)) {
333 my $item_marc = MARC::Record->new();
334 $item_marc->leader("00000 a "); # must set Leader/09 to 'a'
335 $item_marc->append_fields($item_field);
336 $marc_record->delete_field($item_field);
337 my $sth = $dbh->prepare_cached("INSERT INTO import_items (import_record_id, status, marcxml)
338 VALUES (?, ?, ?)");
339 $sth->bind_param(1, $import_record_id);
340 $sth->bind_param(2, 'staged');
341 $sth->bind_param(3, $item_marc->as_xml());
342 $sth->execute();
343 push @import_items_ids, $dbh->{'mysql_insertid'};
344 $sth->finish();
347 if ($#import_items_ids > -1) {
348 _update_batch_record_counts($batch_id) if $update_counts;
349 _update_import_record_marc($import_record_id, $marc_record);
351 return @import_items_ids;
354 =head2 BatchFindBibDuplicates
356 =over 4
358 my $num_with_matches = BatchFindBibDuplicates($batch_id, $matcher, $max_matches, $progress_interval, $progress_callback);
360 =back
362 Goes through the records loaded in the batch and attempts to
363 find duplicates for each one. Sets the matching status
364 of each record to "no_match" or "auto_match" as appropriate.
366 The $max_matches parameter is optional; if it is not supplied,
367 it defaults to 10.
369 The $progress_interval and $progress_callback parameters are
370 optional; if both are supplied, the sub referred to by
371 $progress_callback will be invoked every $progress_interval
372 records using the number of records processed as the
373 singular argument.
375 =cut
377 sub BatchFindBibDuplicates {
378 my $batch_id = shift;
379 my $matcher = shift;
380 my $max_matches = @_ ? shift : 10;
382 # optional callback to monitor status
383 # of job
384 my $progress_interval = 0;
385 my $progress_callback = undef;
386 if ($#_ == 1) {
387 $progress_interval = shift;
388 $progress_callback = shift;
389 $progress_interval = 0 unless $progress_interval =~ /^\d+$/ and $progress_interval > 0;
390 $progress_interval = 0 unless 'CODE' eq ref $progress_callback;
393 my $dbh = C4::Context->dbh;
395 my $sth = $dbh->prepare("SELECT import_record_id, marc
396 FROM import_records
397 JOIN import_biblios USING (import_record_id)
398 WHERE import_batch_id = ?");
399 $sth->execute($batch_id);
400 my $num_with_matches = 0;
401 my $rec_num = 0;
402 while (my $rowref = $sth->fetchrow_hashref) {
403 $rec_num++;
404 if ($progress_interval and (0 == ($rec_num % $progress_interval))) {
405 &$progress_callback($rec_num);
407 my $marc_record = MARC::Record->new_from_usmarc($rowref->{'marc'});
408 my @matches = ();
409 if (defined $matcher) {
410 @matches = $matcher->get_matches($marc_record, $max_matches);
412 if (scalar(@matches) > 0) {
413 $num_with_matches++;
414 SetImportRecordMatches($rowref->{'import_record_id'}, @matches);
415 SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'auto_match');
416 } else {
417 SetImportRecordMatches($rowref->{'import_record_id'}, ());
418 SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'no_match');
421 $sth->finish();
422 return $num_with_matches;
425 =head2 BatchCommitBibRecords
427 =over 4
429 my ($num_added, $num_updated, $num_items_added, $num_items_errored, $num_ignored) =
430 BatchCommitBibRecords($batch_id, $progress_interval, $progress_callback);
432 =back
434 =cut
436 sub BatchCommitBibRecords {
437 my $batch_id = shift;
439 # optional callback to monitor status
440 # of job
441 my $progress_interval = 0;
442 my $progress_callback = undef;
443 if ($#_ == 1) {
444 $progress_interval = shift;
445 $progress_callback = shift;
446 $progress_interval = 0 unless $progress_interval =~ /^\d+$/ and $progress_interval > 0;
447 $progress_interval = 0 unless 'CODE' eq ref $progress_callback;
450 my $num_added = 0;
451 my $num_updated = 0;
452 my $num_items_added = 0;
453 my $num_items_errored = 0;
454 my $num_ignored = 0;
455 # commit (i.e., save, all records in the batch)
456 # FIXME biblio only at the moment
457 SetImportBatchStatus('importing');
458 my $overlay_action = GetImportBatchOverlayAction($batch_id);
459 my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
460 my $item_action = GetImportBatchItemAction($batch_id);
461 my $dbh = C4::Context->dbh;
462 my $sth = $dbh->prepare("SELECT import_record_id, status, overlay_status, marc, encoding
463 FROM import_records
464 JOIN import_biblios USING (import_record_id)
465 WHERE import_batch_id = ?");
466 $sth->execute($batch_id);
467 my $rec_num = 0;
468 while (my $rowref = $sth->fetchrow_hashref) {
469 $rec_num++;
470 if ($progress_interval and (0 == ($rec_num % $progress_interval))) {
471 &$progress_callback($rec_num);
473 if ($rowref->{'status'} eq 'error' or $rowref->{'status'} eq 'imported') {
474 $num_ignored++;
475 next;
478 my $marc_record = MARC::Record->new_from_usmarc($rowref->{'marc'});
480 # remove any item tags - rely on BatchCommitItems
481 my ($item_tag,$item_subfield) = &GetMarcFromKohaField("items.itemnumber",'');
482 foreach my $item_field ($marc_record->field($item_tag)) {
483 $marc_record->delete_field($item_field);
486 # decide what what to do with the bib and item records
487 my ($bib_result, $item_result, $bib_match) =
488 _get_commit_action($overlay_action, $nomatch_action, $item_action,
489 $rowref->{'overlay_status'}, $rowref->{'import_record_id'});
491 if ($bib_result eq 'create_new') {
492 $num_added++;
493 my ($biblionumber, $biblioitemnumber) = AddBiblio($marc_record, '');
494 my $sth = $dbh->prepare_cached("UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?");
495 $sth->execute($biblionumber, $rowref->{'import_record_id'});
496 $sth->finish();
497 if ($item_result eq 'create_new') {
498 my ($bib_items_added, $bib_items_errored) = BatchCommitItems($rowref->{'import_record_id'}, $biblionumber);
499 $num_items_added += $bib_items_added;
500 $num_items_errored += $bib_items_errored;
502 SetImportRecordStatus($rowref->{'import_record_id'}, 'imported');
503 } elsif ($bib_result eq 'replace') {
504 $num_updated++;
505 my $biblionumber = $bib_match;
506 my ($count, $oldbiblio) = GetBiblio($biblionumber);
507 my $oldxml = GetXmlBiblio($biblionumber);
509 # remove item fields so that they don't get
510 # added again if record is reverted
511 my $old_marc = MARC::Record->new_from_xml(StripNonXmlChars($oldxml), 'UTF-8', $rowref->{'encoding'});
512 foreach my $item_field ($old_marc->field($item_tag)) {
513 $old_marc->delete_field($item_field);
516 ModBiblio($marc_record, $biblionumber, $oldbiblio->{'frameworkcode'});
517 my $sth = $dbh->prepare_cached("UPDATE import_records SET marcxml_old = ? WHERE import_record_id = ?");
518 $sth->execute($old_marc->as_xml(), $rowref->{'import_record_id'});
519 $sth->finish();
520 my $sth2 = $dbh->prepare_cached("UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?");
521 $sth2->execute($biblionumber, $rowref->{'import_record_id'});
522 $sth2->finish();
523 if ($item_result eq 'create_new') {
524 my ($bib_items_added, $bib_items_errored) = BatchCommitItems($rowref->{'import_record_id'}, $biblionumber);
525 $num_items_added += $bib_items_added;
526 $num_items_errored += $bib_items_errored;
528 SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'match_applied');
529 SetImportRecordStatus($rowref->{'import_record_id'}, 'imported');
530 } elsif ($bib_result eq 'ignore') {
531 $num_ignored++;
532 my $biblionumber = $bib_match;
533 if (defined $biblionumber and $item_result eq 'create_new') {
534 my ($bib_items_added, $bib_items_errored) = BatchCommitItems($rowref->{'import_record_id'}, $biblionumber);
535 $num_items_added += $bib_items_added;
536 $num_items_errored += $bib_items_errored;
537 # still need to record the matched biblionumber so that the
538 # items can be reverted
539 my $sth2 = $dbh->prepare_cached("UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?");
540 $sth2->execute($biblionumber, $rowref->{'import_record_id'});
541 SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'match_applied');
543 SetImportRecordStatus($rowref->{'import_record_id'}, 'ignored');
546 $sth->finish();
547 SetImportBatchStatus($batch_id, 'imported');
548 return ($num_added, $num_updated, $num_items_added, $num_items_errored, $num_ignored);
551 =head2 BatchCommitItems
553 =over 4
555 ($num_items_added, $num_items_errored) = BatchCommitItems($import_record_id, $biblionumber);
557 =back
559 =cut
561 sub BatchCommitItems {
562 my ($import_record_id, $biblionumber) = @_;
564 my $dbh = C4::Context->dbh;
566 my $num_items_added = 0;
567 my $num_items_errored = 0;
568 my $sth = $dbh->prepare("SELECT import_items_id, import_items.marcxml, encoding
569 FROM import_items
570 JOIN import_records USING (import_record_id)
571 WHERE import_record_id = ?
572 ORDER BY import_items_id");
573 $sth->bind_param(1, $import_record_id);
574 $sth->execute();
575 while (my $row = $sth->fetchrow_hashref()) {
576 my $item_marc = MARC::Record->new_from_xml(StripNonXmlChars($row->{'marcxml'}), 'UTF-8', $row->{'encoding'});
577 # FIXME - duplicate barcode check needs to become part of AddItemFromMarc()
578 my $item = TransformMarcToKoha($dbh, $item_marc);
579 my $duplicate_barcode = exists($item->{'barcode'}) && GetItemnumberFromBarcode($item->{'barcode'});
580 if ($duplicate_barcode) {
581 my $updsth = $dbh->prepare("UPDATE import_items SET status = ?, import_error = ? WHERE import_items_id = ?");
582 $updsth->bind_param(1, 'error');
583 $updsth->bind_param(2, 'duplicate item barcode');
584 $updsth->bind_param(3, $row->{'import_items_id'});
585 $updsth->execute();
586 $num_items_errored++;
587 } else {
588 my ($item_biblionumber, $biblioitemnumber, $itemnumber) = AddItemFromMarc($item_marc, $biblionumber);
589 my $updsth = $dbh->prepare("UPDATE import_items SET status = ?, itemnumber = ? WHERE import_items_id = ?");
590 $updsth->bind_param(1, 'imported');
591 $updsth->bind_param(2, $itemnumber);
592 $updsth->bind_param(3, $row->{'import_items_id'});
593 $updsth->execute();
594 $updsth->finish();
595 $num_items_added++;
598 $sth->finish();
599 return ($num_items_added, $num_items_errored);
602 =head2 BatchRevertBibRecords
604 =over 4
606 my ($num_deleted, $num_errors, $num_reverted, $num_items_deleted, $num_ignored) = BatchRevertBibRecords($batch_id);
608 =back
610 =cut
612 sub BatchRevertBibRecords {
613 my $batch_id = shift;
615 my $num_deleted = 0;
616 my $num_errors = 0;
617 my $num_reverted = 0;
618 my $num_items_deleted = 0;
619 my $num_ignored = 0;
620 # commit (i.e., save, all records in the batch)
621 # FIXME biblio only at the moment
622 SetImportBatchStatus('reverting');
623 my $overlay_action = GetImportBatchOverlayAction($batch_id);
624 my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
625 my $dbh = C4::Context->dbh;
626 my $sth = $dbh->prepare("SELECT import_record_id, status, overlay_status, marcxml_old, encoding, matched_biblionumber
627 FROM import_records
628 JOIN import_biblios USING (import_record_id)
629 WHERE import_batch_id = ?");
630 $sth->execute($batch_id);
631 while (my $rowref = $sth->fetchrow_hashref) {
632 if ($rowref->{'status'} eq 'error' or $rowref->{'status'} eq 'reverted') {
633 $num_ignored++;
634 next;
637 my $bib_result = _get_revert_action($overlay_action, $rowref->{'overlay_status'}, $rowref->{'status'});
639 if ($bib_result eq 'delete') {
640 $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
641 my $error = DelBiblio($rowref->{'matched_biblionumber'});
642 if (defined $error) {
643 $num_errors++;
644 } else {
645 $num_deleted++;
646 SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
648 } elsif ($bib_result eq 'restore') {
649 $num_reverted++;
650 my $old_record = MARC::Record->new_from_xml(StripNonXmlChars($rowref->{'marcxml_old'}), 'UTF-8', $rowref->{'encoding'});
651 my $biblionumber = $rowref->{'matched_biblionumber'};
652 my ($count, $oldbiblio) = GetBiblio($biblionumber);
653 $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
654 ModBiblio($old_record, $biblionumber, $oldbiblio->{'frameworkcode'});
655 SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
656 } elsif ($bib_result eq 'ignore') {
657 $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
658 SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
660 my $sth2 = $dbh->prepare_cached("UPDATE import_biblios SET matched_biblionumber = NULL WHERE import_record_id = ?");
661 $sth2->execute($rowref->{'import_record_id'});
664 $sth->finish();
665 SetImportBatchStatus($batch_id, 'reverted');
666 return ($num_deleted, $num_errors, $num_reverted, $num_items_deleted, $num_ignored);
669 =head2 BatchRevertItems
671 =over 4
673 my $num_items_deleted = BatchRevertItems($import_record_id, $biblionumber);
675 =back
677 =cut
679 sub BatchRevertItems {
680 my ($import_record_id, $biblionumber) = @_;
682 my $dbh = C4::Context->dbh;
683 my $num_items_deleted = 0;
685 my $sth = $dbh->prepare_cached("SELECT import_items_id, itemnumber
686 FROM import_items
687 JOIN items USING (itemnumber)
688 WHERE import_record_id = ?");
689 $sth->bind_param(1, $import_record_id);
690 $sth->execute();
691 while (my $row = $sth->fetchrow_hashref()) {
692 DelItem($dbh, $biblionumber, $row->{'itemnumber'});
693 my $updsth = $dbh->prepare("UPDATE import_items SET status = ? WHERE import_items_id = ?");
694 $updsth->bind_param(1, 'reverted');
695 $updsth->bind_param(2, $row->{'import_items_id'});
696 $updsth->execute();
697 $updsth->finish();
698 $num_items_deleted++;
700 $sth->finish();
701 return $num_items_deleted;
704 =head2 CleanBatch
706 =over 4
708 CleanBatch($batch_id)
710 =back
712 Deletes all staged records from the import batch
713 and sets the status of the batch to 'cleaned'. Note
714 that deleting a stage record does *not* affect
715 any record that has been committed to the database.
717 =cut
719 sub CleanBatch {
720 my $batch_id = shift;
721 return unless defined $batch_id;
723 C4::Context->dbh->do('DELETE FROM import_records WHERE import_batch_id = ?', {}, $batch_id);
724 SetImportBatchStatus($batch_id, 'cleaned');
727 =head2 GetAllImportBatches
729 =over 4
731 my $results = GetAllImportBatches();
733 =back
735 Returns a references to an array of hash references corresponding
736 to all import_batches rows (of batch_type 'batch'), sorted in
737 ascending order by import_batch_id.
739 =cut
741 sub GetAllImportBatches {
742 my $dbh = C4::Context->dbh;
743 my $sth = $dbh->prepare_cached("SELECT * FROM import_batches
744 WHERE batch_type = 'batch'
745 ORDER BY import_batch_id ASC");
747 my $results = [];
748 $sth->execute();
749 while (my $row = $sth->fetchrow_hashref) {
750 push @$results, $row;
752 $sth->finish();
753 return $results;
756 =head2 GetImportBatchRangeDesc
758 =over 4
760 my $results = GetImportBatchRangeDesc($offset, $results_per_group);
762 =back
764 Returns a reference to an array of hash references corresponding to
765 import_batches rows (sorted in descending order by import_batch_id)
766 start at the given offset.
768 =cut
770 sub GetImportBatchRangeDesc {
771 my ($offset, $results_per_group) = @_;
773 my $dbh = C4::Context->dbh;
774 my $query = "SELECT * FROM import_batches
775 WHERE batch_type = 'batch'
776 ORDER BY import_batch_id DESC";
777 my @params;
778 if ($offset){
779 if ($results_per_group){
780 $query .= " LIMIT ?";
781 push(@params, $results_per_group);
783 $query .= " OFFSET ?";
784 push(@params, $offset);
786 my $sth = $dbh->prepare_cached($query);
787 $sth->execute(@params);
788 my $results = $sth->fetchall_arrayref({});
789 $sth->finish();
790 return $results;
793 =head2 GetItemNumbersFromImportBatch
795 =cut
797 sub GetItemNumbersFromImportBatch {
798 my ($batch_id) = @_;
799 my $dbh = C4::Context->dbh;
800 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=?");
801 $sth->execute($batch_id);
802 my @items ;
803 while ( my ($itm) = $sth->fetchrow_array ) {
804 push @items, $itm;
806 return @items;
809 =head2 GetNumberOfImportBatches
811 =over 4
813 my $count = GetNumberOfImportBatches();
815 =back
817 =cut
819 sub GetNumberOfNonZ3950ImportBatches {
820 my $dbh = C4::Context->dbh;
821 my $sth = $dbh->prepare("SELECT COUNT(*) FROM import_batches WHERE batch_type='batch'");
822 $sth->execute();
823 my ($count) = $sth->fetchrow_array();
824 $sth->finish();
825 return $count;
828 =head2 GetImportBibliosRange
830 =over 4
832 my $results = GetImportBibliosRange($batch_id, $offset, $results_per_group);
834 =back
836 Returns a reference to an array of hash references corresponding to
837 import_biblios/import_records rows for a given batch
838 starting at the given offset.
840 =cut
842 sub GetImportBibliosRange {
843 my ($batch_id, $offset, $results_per_group, $status) = @_;
845 my $dbh = C4::Context->dbh;
846 my $query = "SELECT title, author, isbn, issn, import_record_id, record_sequence,
847 status, overlay_status
848 FROM import_records
849 JOIN import_biblios USING (import_record_id)
850 WHERE import_batch_id = ?";
851 my @params;
852 push(@params, $batch_id);
853 if ($status) {
854 $query .= " AND status=?";
855 push(@params,$status);
857 $query.=" ORDER BY import_record_id";
859 if($offset){
860 if($results_per_group){
861 $query .= " LIMIT ?";
862 push(@params, $results_per_group);
864 $query .= " OFFSET ?";
865 push(@params, $offset);
867 my $sth = $dbh->prepare_cached($query);
868 $sth->execute(@params);
869 my $results = $sth->fetchall_arrayref({});
870 $sth->finish();
871 return $results;
875 =head2 GetBestRecordMatch
877 =over 4
879 my $record_id = GetBestRecordMatch($import_record_id);
881 =back
883 =cut
885 sub GetBestRecordMatch {
886 my ($import_record_id) = @_;
888 my $dbh = C4::Context->dbh;
889 my $sth = $dbh->prepare("SELECT candidate_match_id
890 FROM import_record_matches
891 WHERE import_record_id = ?
892 ORDER BY score DESC, candidate_match_id DESC");
893 $sth->execute($import_record_id);
894 my ($record_id) = $sth->fetchrow_array();
895 $sth->finish();
896 return $record_id;
899 =head2 GetImportBatchStatus
901 =over 4
903 my $status = GetImportBatchStatus($batch_id);
905 =back
907 =cut
909 sub GetImportBatchStatus {
910 my ($batch_id) = @_;
912 my $dbh = C4::Context->dbh;
913 my $sth = $dbh->prepare("SELECT import_status FROM import_batches WHERE import_batch_id = ?");
914 $sth->execute($batch_id);
915 my ($status) = $sth->fetchrow_array();
916 $sth->finish();
917 return $status;
921 =head2 SetImportBatchStatus
923 =over 4
925 SetImportBatchStatus($batch_id, $new_status);
927 =back
929 =cut
931 sub SetImportBatchStatus {
932 my ($batch_id, $new_status) = @_;
934 my $dbh = C4::Context->dbh;
935 my $sth = $dbh->prepare("UPDATE import_batches SET import_status = ? WHERE import_batch_id = ?");
936 $sth->execute($new_status, $batch_id);
937 $sth->finish();
941 =head2 GetImportBatchOverlayAction
943 =over 4
945 my $overlay_action = GetImportBatchOverlayAction($batch_id);
947 =back
949 =cut
951 sub GetImportBatchOverlayAction {
952 my ($batch_id) = @_;
954 my $dbh = C4::Context->dbh;
955 my $sth = $dbh->prepare("SELECT overlay_action FROM import_batches WHERE import_batch_id = ?");
956 $sth->execute($batch_id);
957 my ($overlay_action) = $sth->fetchrow_array();
958 $sth->finish();
959 return $overlay_action;
964 =head2 SetImportBatchOverlayAction
966 =over 4
968 SetImportBatchOverlayAction($batch_id, $new_overlay_action);
970 =back
972 =cut
974 sub SetImportBatchOverlayAction {
975 my ($batch_id, $new_overlay_action) = @_;
977 my $dbh = C4::Context->dbh;
978 my $sth = $dbh->prepare("UPDATE import_batches SET overlay_action = ? WHERE import_batch_id = ?");
979 $sth->execute($new_overlay_action, $batch_id);
980 $sth->finish();
984 =head2 GetImportBatchNoMatchAction
986 =over 4
988 my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
990 =back
992 =cut
994 sub GetImportBatchNoMatchAction {
995 my ($batch_id) = @_;
997 my $dbh = C4::Context->dbh;
998 my $sth = $dbh->prepare("SELECT nomatch_action FROM import_batches WHERE import_batch_id = ?");
999 $sth->execute($batch_id);
1000 my ($nomatch_action) = $sth->fetchrow_array();
1001 $sth->finish();
1002 return $nomatch_action;
1007 =head2 SetImportBatchNoMatchAction
1009 =over 4
1011 SetImportBatchNoMatchAction($batch_id, $new_nomatch_action);
1013 =back
1015 =cut
1017 sub SetImportBatchNoMatchAction {
1018 my ($batch_id, $new_nomatch_action) = @_;
1020 my $dbh = C4::Context->dbh;
1021 my $sth = $dbh->prepare("UPDATE import_batches SET nomatch_action = ? WHERE import_batch_id = ?");
1022 $sth->execute($new_nomatch_action, $batch_id);
1023 $sth->finish();
1027 =head2 GetImportBatchItemAction
1029 =over 4
1031 my $item_action = GetImportBatchItemAction($batch_id);
1033 =back
1035 =cut
1037 sub GetImportBatchItemAction {
1038 my ($batch_id) = @_;
1040 my $dbh = C4::Context->dbh;
1041 my $sth = $dbh->prepare("SELECT item_action FROM import_batches WHERE import_batch_id = ?");
1042 $sth->execute($batch_id);
1043 my ($item_action) = $sth->fetchrow_array();
1044 $sth->finish();
1045 return $item_action;
1050 =head2 SetImportBatchItemAction
1052 =over 4
1054 SetImportBatchItemAction($batch_id, $new_item_action);
1056 =back
1058 =cut
1060 sub SetImportBatchItemAction {
1061 my ($batch_id, $new_item_action) = @_;
1063 my $dbh = C4::Context->dbh;
1064 my $sth = $dbh->prepare("UPDATE import_batches SET item_action = ? WHERE import_batch_id = ?");
1065 $sth->execute($new_item_action, $batch_id);
1066 $sth->finish();
1070 =head2 GetImportBatchMatcher
1072 =over 4
1074 my $matcher_id = GetImportBatchMatcher($batch_id);
1076 =back
1078 =cut
1080 sub GetImportBatchMatcher {
1081 my ($batch_id) = @_;
1083 my $dbh = C4::Context->dbh;
1084 my $sth = $dbh->prepare("SELECT matcher_id FROM import_batches WHERE import_batch_id = ?");
1085 $sth->execute($batch_id);
1086 my ($matcher_id) = $sth->fetchrow_array();
1087 $sth->finish();
1088 return $matcher_id;
1093 =head2 SetImportBatchMatcher
1095 =over 4
1097 SetImportBatchMatcher($batch_id, $new_matcher_id);
1099 =back
1101 =cut
1103 sub SetImportBatchMatcher {
1104 my ($batch_id, $new_matcher_id) = @_;
1106 my $dbh = C4::Context->dbh;
1107 my $sth = $dbh->prepare("UPDATE import_batches SET matcher_id = ? WHERE import_batch_id = ?");
1108 $sth->execute($new_matcher_id, $batch_id);
1109 $sth->finish();
1113 =head2 GetImportRecordOverlayStatus
1115 =over 4
1117 my $overlay_status = GetImportRecordOverlayStatus($import_record_id);
1119 =back
1121 =cut
1123 sub GetImportRecordOverlayStatus {
1124 my ($import_record_id) = @_;
1126 my $dbh = C4::Context->dbh;
1127 my $sth = $dbh->prepare("SELECT overlay_status FROM import_records WHERE import_record_id = ?");
1128 $sth->execute($import_record_id);
1129 my ($overlay_status) = $sth->fetchrow_array();
1130 $sth->finish();
1131 return $overlay_status;
1136 =head2 SetImportRecordOverlayStatus
1138 =over 4
1140 SetImportRecordOverlayStatus($import_record_id, $new_overlay_status);
1142 =back
1144 =cut
1146 sub SetImportRecordOverlayStatus {
1147 my ($import_record_id, $new_overlay_status) = @_;
1149 my $dbh = C4::Context->dbh;
1150 my $sth = $dbh->prepare("UPDATE import_records SET overlay_status = ? WHERE import_record_id = ?");
1151 $sth->execute($new_overlay_status, $import_record_id);
1152 $sth->finish();
1156 =head2 GetImportRecordStatus
1158 =over 4
1160 my $overlay_status = GetImportRecordStatus($import_record_id);
1162 =back
1164 =cut
1166 sub GetImportRecordStatus {
1167 my ($import_record_id) = @_;
1169 my $dbh = C4::Context->dbh;
1170 my $sth = $dbh->prepare("SELECT status FROM import_records WHERE import_record_id = ?");
1171 $sth->execute($import_record_id);
1172 my ($overlay_status) = $sth->fetchrow_array();
1173 $sth->finish();
1174 return $overlay_status;
1179 =head2 SetImportRecordStatus
1181 =over 4
1183 SetImportRecordStatus($import_record_id, $new_overlay_status);
1185 =back
1187 =cut
1189 sub SetImportRecordStatus {
1190 my ($import_record_id, $new_overlay_status) = @_;
1192 my $dbh = C4::Context->dbh;
1193 my $sth = $dbh->prepare("UPDATE import_records SET status = ? WHERE import_record_id = ?");
1194 $sth->execute($new_overlay_status, $import_record_id);
1195 $sth->finish();
1199 =head2 GetImportRecordMatches
1201 =over 4
1203 my $results = GetImportRecordMatches($import_record_id, $best_only);
1205 =back
1207 =cut
1209 sub GetImportRecordMatches {
1210 my $import_record_id = shift;
1211 my $best_only = @_ ? shift : 0;
1213 my $dbh = C4::Context->dbh;
1214 # FIXME currently biblio only
1215 my $sth = $dbh->prepare_cached("SELECT title, author, biblionumber, score
1216 FROM import_records
1217 JOIN import_record_matches USING (import_record_id)
1218 JOIN biblio ON (biblionumber = candidate_match_id)
1219 WHERE import_record_id = ?
1220 ORDER BY score DESC, biblionumber DESC");
1221 $sth->bind_param(1, $import_record_id);
1222 my $results = [];
1223 $sth->execute();
1224 while (my $row = $sth->fetchrow_hashref) {
1225 push @$results, $row;
1226 last if $best_only;
1228 $sth->finish();
1230 return $results;
1235 =head2 SetImportRecordMatches
1237 =over 4
1239 SetImportRecordMatches($import_record_id, @matches);
1241 =back
1243 =cut
1245 sub SetImportRecordMatches {
1246 my $import_record_id = shift;
1247 my @matches = @_;
1249 my $dbh = C4::Context->dbh;
1250 my $delsth = $dbh->prepare("DELETE FROM import_record_matches WHERE import_record_id = ?");
1251 $delsth->execute($import_record_id);
1252 $delsth->finish();
1254 my $sth = $dbh->prepare("INSERT INTO import_record_matches (import_record_id, candidate_match_id, score)
1255 VALUES (?, ?, ?)");
1256 foreach my $match (@matches) {
1257 $sth->execute($import_record_id, $match->{'record_id'}, $match->{'score'});
1262 # internal functions
1264 sub _create_import_record {
1265 my ($batch_id, $record_sequence, $marc_record, $record_type, $encoding, $z3950random) = @_;
1267 my $dbh = C4::Context->dbh;
1268 my $sth = $dbh->prepare("INSERT INTO import_records (import_batch_id, record_sequence, marc, marcxml,
1269 record_type, encoding, z3950random)
1270 VALUES (?, ?, ?, ?, ?, ?, ?)");
1271 $sth->execute($batch_id, $record_sequence, $marc_record->as_usmarc(), $marc_record->as_xml(),
1272 $record_type, $encoding, $z3950random);
1273 my $import_record_id = $dbh->{'mysql_insertid'};
1274 $sth->finish();
1275 return $import_record_id;
1278 sub _update_import_record_marc {
1279 my ($import_record_id, $marc_record) = @_;
1281 my $dbh = C4::Context->dbh;
1282 my $sth = $dbh->prepare("UPDATE import_records SET marc = ?, marcxml = ?
1283 WHERE import_record_id = ?");
1284 $sth->execute($marc_record->as_usmarc(), $marc_record->as_xml(), $import_record_id);
1285 $sth->finish();
1288 sub _add_biblio_fields {
1289 my ($import_record_id, $marc_record) = @_;
1291 my ($title, $author, $isbn, $issn) = _parse_biblio_fields($marc_record);
1292 my $dbh = C4::Context->dbh;
1293 # FIXME no controlnumber, originalsource
1294 $isbn = C4::Koha::_isbn_cleanup($isbn); # FIXME C4::Koha::_isbn_cleanup should be made public
1295 my $sth = $dbh->prepare("INSERT INTO import_biblios (import_record_id, title, author, isbn, issn) VALUES (?, ?, ?, ?, ?)");
1296 $sth->execute($import_record_id, $title, $author, $isbn, $issn);
1297 $sth->finish();
1301 sub _update_biblio_fields {
1302 my ($import_record_id, $marc_record) = @_;
1304 my ($title, $author, $isbn, $issn) = _parse_biblio_fields($marc_record);
1305 my $dbh = C4::Context->dbh;
1306 # FIXME no controlnumber, originalsource
1307 # FIXME 2 - should regularize normalization of ISBN wherever it is done
1308 $isbn =~ s/\(.*$//;
1309 $isbn =~ tr/ -_//;
1310 $isbn = uc $isbn;
1311 my $sth = $dbh->prepare("UPDATE import_biblios SET title = ?, author = ?, isbn = ?, issn = ?
1312 WHERE import_record_id = ?");
1313 $sth->execute($title, $author, $isbn, $issn, $import_record_id);
1314 $sth->finish();
1317 sub _parse_biblio_fields {
1318 my ($marc_record) = @_;
1320 my $dbh = C4::Context->dbh;
1321 my $bibliofields = TransformMarcToKoha($dbh, $marc_record, '');
1322 return ($bibliofields->{'title'}, $bibliofields->{'author'}, $bibliofields->{'isbn'}, $bibliofields->{'issn'});
1326 sub _update_batch_record_counts {
1327 my ($batch_id) = @_;
1329 my $dbh = C4::Context->dbh;
1330 my $sth = $dbh->prepare_cached("UPDATE import_batches SET num_biblios = (
1331 SELECT COUNT(*)
1332 FROM import_records
1333 WHERE import_batch_id = import_batches.import_batch_id
1334 AND record_type = 'biblio')
1335 WHERE import_batch_id = ?");
1336 $sth->bind_param(1, $batch_id);
1337 $sth->execute();
1338 $sth->finish();
1339 $sth = $dbh->prepare_cached("UPDATE import_batches SET num_items = (
1340 SELECT COUNT(*)
1341 FROM import_records
1342 JOIN import_items USING (import_record_id)
1343 WHERE import_batch_id = import_batches.import_batch_id
1344 AND record_type = 'biblio')
1345 WHERE import_batch_id = ?");
1346 $sth->bind_param(1, $batch_id);
1347 $sth->execute();
1348 $sth->finish();
1352 sub _get_commit_action {
1353 my ($overlay_action, $nomatch_action, $item_action, $overlay_status, $import_record_id) = @_;
1355 my ($bib_result, $bib_match, $item_result);
1357 if ($overlay_status ne 'no_match') {
1358 $bib_match = GetBestRecordMatch($import_record_id);
1359 if ($overlay_action eq 'replace') {
1360 $bib_result = defined($bib_match) ? 'replace' : 'create_new';
1361 } elsif ($overlay_action eq 'create_new') {
1362 $bib_result = 'create_new';
1363 } elsif ($overlay_action eq 'ignore') {
1364 $bib_result = 'ignore';
1366 $item_result = ($item_action eq 'always_add' or $item_action eq 'add_only_for_matches') ? 'create_new' : 'ignore';
1367 } else {
1368 $bib_result = $nomatch_action;
1369 $item_result = ($item_action eq 'always_add' or $item_action eq 'add_only_for_new') ? 'create_new' : 'ignore';
1372 return ($bib_result, $item_result, $bib_match);
1375 sub _get_revert_action {
1376 my ($overlay_action, $overlay_status, $status) = @_;
1378 my $bib_result;
1380 if ($status eq 'ignored') {
1381 $bib_result = 'ignore';
1382 } else {
1383 if ($overlay_action eq 'create_new') {
1384 $bib_result = 'delete';
1385 } else {
1386 $bib_result = ($overlay_status eq 'match_applied') ? 'restore' : 'delete';
1389 return $bib_result;
1393 __END__
1395 =head1 AUTHOR
1397 Koha Development Team <info@koha.org>
1399 Galen Charlton <galen.charlton@liblime.com>
1401 =cut