bug 2423: actually ignore already-imported records
[koha.git] / C4 / ImportBatch.pm
1 package C4::ImportBatch;
2
3 # Copyright (C) 2007 LibLime
4 #
5 # This file is part of Koha.
6 #
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.
11 #
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.
15 #
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
19
20 use strict;
21 use C4::Context;
22 use C4::Koha;
23 use C4::Biblio;
24 use C4::Items;
25 use C4::Charset;
26
27 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
28
29 BEGIN {
30         # set the version for version checking
31         $VERSION = 3.01;
32         require Exporter;
33         @ISA    = qw(Exporter);
34         @EXPORT = qw(
35     GetZ3950BatchId
36     GetImportRecordMarc
37     AddImportBatch
38     GetImportBatch
39     AddBiblioToBatch
40     ModBiblioInBatch
41
42     BatchStageMarcRecords
43     BatchFindBibDuplicates
44     BatchCommitBibRecords
45     BatchRevertBibRecords
46
47     GetAllImportBatches
48     GetImportBatchRangeDesc
49     GetNumberOfNonZ3950ImportBatches
50     GetImportBibliosRange
51         GetItemNumbersFromImportBatch
52     
53     GetImportBatchStatus
54     SetImportBatchStatus
55     GetImportBatchOverlayAction
56     SetImportBatchOverlayAction
57     GetImportBatchNoMatchAction
58     SetImportBatchNoMatchAction
59     GetImportBatchItemAction
60     SetImportBatchItemAction
61     GetImportBatchMatcher
62     SetImportBatchMatcher
63     GetImportRecordOverlayStatus
64     SetImportRecordOverlayStatus
65     GetImportRecordStatus
66     SetImportRecordStatus
67     GetImportRecordMatches
68     SetImportRecordMatches
69         );
70 }
71
72 =head1 NAME
73
74 C4::ImportBatch - manage batches of imported MARC records
75
76 =head1 SYNOPSIS
77
78 =over 4
79
80 use C4::ImportBatch;
81
82 =back
83
84 =head1 FUNCTIONS
85
86 =head2 GetZ3950BatchId
87
88 =over 4
89
90 my $batchid = GetZ3950BatchId($z3950server);
91
92 =back
93
94 Retrieves the ID of the import batch for the Z39.50
95 reservoir for the given target.  If necessary,
96 creates the import batch.
97
98 =cut
99
100 sub GetZ3950BatchId {
101     my ($z3950server) = @_;
102
103     my $dbh = C4::Context->dbh;
104     my $sth = $dbh->prepare("SELECT import_batch_id FROM import_batches
105                              WHERE  batch_type = 'z3950'
106                              AND    file_name = ?");
107     $sth->execute($z3950server);
108     my $rowref = $sth->fetchrow_arrayref();
109     $sth->finish();
110     if (defined $rowref) {
111         return $rowref->[0];
112     } else {
113         my $batch_id = AddImportBatch('create_new', 'staged', 'z3950', $z3950server, '');
114         return $batch_id;
115     }
116     
117 }
118
119 =head2 GetImportRecordMarc
120
121 =over 4
122
123 my ($marcblob, $encoding) = GetImportRecordMarc($import_record_id);
124
125 =back
126
127 =cut
128
129 sub GetImportRecordMarc {
130     my ($import_record_id) = @_;
131
132     my $dbh = C4::Context->dbh;
133     my $sth = $dbh->prepare("SELECT marc, encoding FROM import_records WHERE import_record_id = ?");
134     $sth->execute($import_record_id);
135     my ($marc, $encoding) = $sth->fetchrow();
136     $sth->finish();
137     return $marc;
138
139 }
140
141 =head2 AddImportBatch
142
143 =over 4
144
145 my $batch_id = AddImportBatch($overlay_action, $import_status, $type, $file_name, $comments);
146
147 =back
148
149 =cut
150
151 sub AddImportBatch {
152     my ($overlay_action, $import_status, $type, $file_name, $comments) = @_;
153
154     my $dbh = C4::Context->dbh;
155     my $sth = $dbh->prepare("INSERT INTO import_batches (overlay_action, import_status, batch_type,
156                                                          file_name, comments)
157                                     VALUES (?, ?, ?, ?, ?)");
158     $sth->execute($overlay_action, $import_status, $type, $file_name, $comments);
159     my $batch_id = $dbh->{'mysql_insertid'};
160     $sth->finish();
161
162     return $batch_id;
163
164 }
165
166 =head2 GetImportBatch 
167
168 =over 4
169
170 my $row = GetImportBatch($batch_id);
171
172 =back
173
174 Retrieve a hashref of an import_batches row.
175
176 =cut
177
178 sub GetImportBatch {
179     my ($batch_id) = @_;
180
181     my $dbh = C4::Context->dbh;
182     my $sth = $dbh->prepare_cached("SELECT * FROM import_batches WHERE import_batch_id = ?");
183     $sth->bind_param(1, $batch_id);
184     $sth->execute();
185     my $result = $sth->fetchrow_hashref;
186     $sth->finish();
187     return $result;
188
189 }
190
191 =head2 AddBiblioToBatch 
192
193 =over 4
194
195 my $import_record_id = AddBiblioToBatch($batch_id, $record_sequence, $marc_record, $encoding, $z3950random, $update_counts);
196
197 =back
198
199 =cut
200
201 sub AddBiblioToBatch {
202     my $batch_id = shift;
203     my $record_sequence = shift;
204     my $marc_record = shift;
205     my $encoding = shift;
206     my $z3950random = shift;
207     my $update_counts = @_ ? shift : 1;
208
209     my $import_record_id = _create_import_record($batch_id, $record_sequence, $marc_record, 'biblio', $encoding, $z3950random);
210     _add_biblio_fields($import_record_id, $marc_record);
211     _update_batch_record_counts($batch_id) if $update_counts;
212     return $import_record_id;
213 }
214
215 =head2 ModBiblioInBatch
216
217 =over 4
218
219 ModBiblioInBatch($import_record_id, $marc_record);
220
221 =back
222
223 =cut
224
225 sub ModBiblioInBatch {
226     my ($import_record_id, $marc_record) = @_;
227
228     _update_import_record_marc($import_record_id, $marc_record);
229     _update_biblio_fields($import_record_id, $marc_record);
230
231 }
232
233 =head2 BatchStageMarcRecords
234
235 =over 4
236
237 ($batch_id, $num_records, $num_items, @invalid_records) = 
238     BatchStageMarcRecords($marc_flavor, $marc_records, $file_name, 
239                           $comments, $branch_code, $parse_items,
240                           $leave_as_staging, 
241                           $progress_interval, $progress_callback);
242
243 =back
244
245 =cut
246
247 sub  BatchStageMarcRecords {
248     my $marc_flavor = shift;
249     my $marc_records = shift;
250     my $file_name = shift;
251     my $comments = shift;
252     my $branch_code = shift;
253     my $parse_items = shift;
254     my $leave_as_staging = shift;
255    
256     # optional callback to monitor status 
257     # of job
258     my $progress_interval = 0;
259     my $progress_callback = undef;
260     if ($#_ == 1) {
261         $progress_interval = shift;
262         $progress_callback = shift;
263         $progress_interval = 0 unless $progress_interval =~ /^\d+$/ and $progress_interval > 0;
264         $progress_interval = 0 unless 'CODE' eq ref $progress_callback;
265     } 
266     
267     my $batch_id = AddImportBatch('create_new', 'staging', 'batch', $file_name, $comments);
268     if ($parse_items) {
269         SetImportBatchItemAction($batch_id, 'always_add');
270     } else {
271         SetImportBatchItemAction($batch_id, 'ignore');
272     }
273
274     my @invalid_records = ();
275     my $num_valid = 0;
276     my $num_items = 0;
277     # FIXME - for now, we're dealing only with bibs
278     my $rec_num = 0;
279     foreach my $marc_blob (split(/\x1D/, $marc_records)) {
280         $marc_blob =~ s/^\s+//g;
281         $marc_blob =~ s/\s+$//g;
282         next unless $marc_blob;
283         $rec_num++;
284         if ($progress_interval and (0 == ($rec_num % $progress_interval))) {
285             &$progress_callback($rec_num);
286         }
287         my ($marc_record, $charset_guessed, $char_errors) =
288             MarcToUTF8Record($marc_blob, C4::Context->preference("marcflavour"));
289         my $import_record_id;
290         if (scalar($marc_record->fields()) == 0) {
291             push @invalid_records, $marc_blob;
292         } else {
293             $num_valid++;
294             $import_record_id = AddBiblioToBatch($batch_id, $rec_num, $marc_record, $marc_flavor, int(rand(99999)), 0);
295             if ($parse_items) {
296                 my @import_items_ids = AddItemsToImportBiblio($batch_id, $import_record_id, $marc_record, 0);
297                 $num_items += scalar(@import_items_ids);
298             }
299         }
300     }
301     unless ($leave_as_staging) {
302         SetImportBatchStatus($batch_id, 'staged');
303     }
304     # FIXME branch_code, number of bibs, number of items
305     _update_batch_record_counts($batch_id);
306     return ($batch_id, $num_valid, $num_items, @invalid_records);
307 }
308
309 =head2 AddItemsToImportBiblio
310
311 =over 4
312
313 my @import_items_ids = AddItemsToImportBiblio($batch_id, $import_record_id, $marc_record, $update_counts);
314
315 =back
316
317 =cut
318
319 sub AddItemsToImportBiblio {
320     my $batch_id = shift;
321     my $import_record_id = shift;
322     my $marc_record = shift;
323     my $update_counts = @_ ? shift : 0;
324
325     my @import_items_ids = ();
326    
327     my $dbh = C4::Context->dbh; 
328     my ($item_tag,$item_subfield) = &GetMarcFromKohaField("items.itemnumber",'');
329     foreach my $item_field ($marc_record->field($item_tag)) {
330         my $item_marc = MARC::Record->new();
331         $item_marc->leader("00000    a              "); # must set Leader/09 to 'a'
332         $item_marc->append_fields($item_field);
333         $marc_record->delete_field($item_field);
334         my $sth = $dbh->prepare_cached("INSERT INTO import_items (import_record_id, status, marcxml)
335                                         VALUES (?, ?, ?)");
336         $sth->bind_param(1, $import_record_id);
337         $sth->bind_param(2, 'staged');
338         $sth->bind_param(3, $item_marc->as_xml());
339         $sth->execute();
340         push @import_items_ids, $dbh->{'mysql_insertid'};
341         $sth->finish();
342     }
343
344     if ($#import_items_ids > -1) {
345         _update_batch_record_counts($batch_id) if $update_counts;
346         _update_import_record_marc($import_record_id, $marc_record);
347     }
348     return @import_items_ids;
349 }
350
351 =head2 BatchFindBibDuplicates
352
353 =over 4
354
355 my $num_with_matches = BatchFindBibDuplicates($batch_id, $matcher, $max_matches, $progress_interval, $progress_callback);
356
357 =back
358
359 Goes through the records loaded in the batch and attempts to 
360 find duplicates for each one.  Sets the matching status 
361 of each record to "no_match" or "auto_match" as appropriate.
362
363 The $max_matches parameter is optional; if it is not supplied,
364 it defaults to 10.
365
366 The $progress_interval and $progress_callback parameters are 
367 optional; if both are supplied, the sub referred to by
368 $progress_callback will be invoked every $progress_interval
369 records using the number of records processed as the 
370 singular argument.
371
372 =cut
373
374 sub BatchFindBibDuplicates {
375     my $batch_id = shift;
376     my $matcher = shift;
377     my $max_matches = @_ ? shift : 10;
378
379     # optional callback to monitor status 
380     # of job
381     my $progress_interval = 0;
382     my $progress_callback = undef;
383     if ($#_ == 1) {
384         $progress_interval = shift;
385         $progress_callback = shift;
386         $progress_interval = 0 unless $progress_interval =~ /^\d+$/ and $progress_interval > 0;
387         $progress_interval = 0 unless 'CODE' eq ref $progress_callback;
388     }
389
390     my $dbh = C4::Context->dbh;
391
392     my $sth = $dbh->prepare("SELECT import_record_id, marc
393                              FROM import_records
394                              JOIN import_biblios USING (import_record_id)
395                              WHERE import_batch_id = ?");
396     $sth->execute($batch_id);
397     my $num_with_matches = 0;
398     my $rec_num = 0;
399     while (my $rowref = $sth->fetchrow_hashref) {
400         $rec_num++;
401         if ($progress_interval and (0 == ($rec_num % $progress_interval))) {
402             &$progress_callback($rec_num);
403         }
404         my $marc_record = MARC::Record->new_from_usmarc($rowref->{'marc'});
405         my @matches = ();
406         if (defined $matcher) {
407             @matches = $matcher->get_matches($marc_record, $max_matches);
408         }
409         if (scalar(@matches) > 0) {
410             $num_with_matches++;
411             SetImportRecordMatches($rowref->{'import_record_id'}, @matches);
412             SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'auto_match');
413         } else {
414             SetImportRecordMatches($rowref->{'import_record_id'}, ());
415             SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'no_match');
416         }
417     }
418     $sth->finish();
419     return $num_with_matches;
420 }
421
422 =head2 BatchCommitBibRecords
423
424 =over 4
425
426 my ($num_added, $num_updated, $num_items_added, $num_items_errored, $num_ignored) = 
427     BatchCommitBibRecords($batch_id, $progress_interval, $progress_callback);
428
429 =back
430
431 =cut
432
433 sub BatchCommitBibRecords {
434     my $batch_id = shift;
435
436     # optional callback to monitor status 
437     # of job
438     my $progress_interval = 0;
439     my $progress_callback = undef;
440     if ($#_ == 1) {
441         $progress_interval = shift;
442         $progress_callback = shift;
443         $progress_interval = 0 unless $progress_interval =~ /^\d+$/ and $progress_interval > 0;
444         $progress_interval = 0 unless 'CODE' eq ref $progress_callback;
445     }
446
447     my $num_added = 0;
448     my $num_updated = 0;
449     my $num_items_added = 0;
450     my $num_items_errored = 0;
451     my $num_ignored = 0;
452     # commit (i.e., save, all records in the batch)
453     # FIXME biblio only at the moment
454     SetImportBatchStatus('importing');
455     my $overlay_action = GetImportBatchOverlayAction($batch_id);
456     my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
457     my $item_action = GetImportBatchItemAction($batch_id);
458     my $dbh = C4::Context->dbh;
459     my $sth = $dbh->prepare("SELECT import_record_id, status, overlay_status, marc, encoding
460                              FROM import_records
461                              JOIN import_biblios USING (import_record_id)
462                              WHERE import_batch_id = ?");
463     $sth->execute($batch_id);
464     my $rec_num = 0;
465     while (my $rowref = $sth->fetchrow_hashref) {
466         $rec_num++;
467         if ($progress_interval and (0 == ($rec_num % $progress_interval))) {
468             &$progress_callback($rec_num);
469         }
470         if ($rowref->{'status'} eq 'error' or $rowref->{'status'} eq 'imported') {
471             $num_ignored++;
472             next;
473         }
474
475         my $marc_record = MARC::Record->new_from_usmarc($rowref->{'marc'});
476
477         # remove any item tags - rely on BatchCommitItems
478         my ($item_tag,$item_subfield) = &GetMarcFromKohaField("items.itemnumber",'');
479         foreach my $item_field ($marc_record->field($item_tag)) {
480             $marc_record->delete_field($item_field);
481         }
482
483         # decide what what to do with the bib and item records
484         my ($bib_result, $item_result, $bib_match) = 
485             _get_commit_action($overlay_action, $nomatch_action, $item_action, 
486                                $rowref->{'overlay_status'}, $rowref->{'import_record_id'});
487
488         if ($bib_result eq 'create_new') {
489             $num_added++;
490             my ($biblionumber, $biblioitemnumber) = AddBiblio($marc_record, '');
491             my $sth = $dbh->prepare_cached("UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?");
492             $sth->execute($biblionumber, $rowref->{'import_record_id'});
493             $sth->finish();
494             if ($item_result eq 'create_new') {
495                 my ($bib_items_added, $bib_items_errored) = BatchCommitItems($rowref->{'import_record_id'}, $biblionumber);
496                 $num_items_added += $bib_items_added;
497                 $num_items_errored += $bib_items_errored;
498             }
499             SetImportRecordStatus($rowref->{'import_record_id'}, 'imported');
500         } elsif ($bib_result eq 'replace') {
501             $num_updated++;
502             my $biblionumber = $bib_match;
503             my ($count, $oldbiblio) = GetBiblio($biblionumber);
504             my $oldxml = GetXmlBiblio($biblionumber);
505
506             # remove item fields so that they don't get
507             # added again if record is reverted
508             my $old_marc = MARC::Record->new_from_xml(StripNonXmlChars($oldxml), 'UTF-8', $rowref->{'encoding'});
509             foreach my $item_field ($old_marc->field($item_tag)) {
510                 $old_marc->delete_field($item_field);
511             }
512
513             ModBiblio($marc_record, $biblionumber, $oldbiblio->{'frameworkcode'});
514             my $sth = $dbh->prepare_cached("UPDATE import_records SET marcxml_old = ? WHERE import_record_id = ?");
515             $sth->execute($old_marc->as_xml(), $rowref->{'import_record_id'});
516             $sth->finish();
517             my $sth2 = $dbh->prepare_cached("UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?");
518             $sth2->execute($biblionumber, $rowref->{'import_record_id'});
519             $sth2->finish();
520             if ($item_result eq 'create_new') {
521                 my ($bib_items_added, $bib_items_errored) = BatchCommitItems($rowref->{'import_record_id'}, $biblionumber);
522                 $num_items_added += $bib_items_added;
523                 $num_items_errored += $bib_items_errored;
524             }
525             SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'match_applied');
526             SetImportRecordStatus($rowref->{'import_record_id'}, 'imported');
527         } elsif ($bib_result eq 'ignore') {
528             $num_ignored++;
529             my $biblionumber = $bib_match;
530             if (defined $biblionumber and $item_result eq 'create_new') {
531                 my ($bib_items_added, $bib_items_errored) = BatchCommitItems($rowref->{'import_record_id'}, $biblionumber);
532                 $num_items_added += $bib_items_added;
533                 $num_items_errored += $bib_items_errored;
534                 # still need to record the matched biblionumber so that the
535                 # items can be reverted
536                 my $sth2 = $dbh->prepare_cached("UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?");
537                 $sth2->execute($biblionumber, $rowref->{'import_record_id'});
538                 SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'match_applied');
539             }
540             SetImportRecordStatus($rowref->{'import_record_id'}, 'ignored');
541         }
542     }
543     $sth->finish();
544     SetImportBatchStatus($batch_id, 'imported');
545     return ($num_added, $num_updated, $num_items_added, $num_items_errored, $num_ignored);
546 }
547
548 =head2 BatchCommitItems
549
550 =over 4
551
552 ($num_items_added, $num_items_errored) = BatchCommitItems($import_record_id, $biblionumber);
553
554 =back
555
556 =cut
557
558 sub BatchCommitItems {
559     my ($import_record_id, $biblionumber) = @_;
560
561     my $dbh = C4::Context->dbh;
562
563     my $num_items_added = 0;
564     my $num_items_errored = 0;
565     my $sth = $dbh->prepare("SELECT import_items_id, import_items.marcxml, encoding
566                              FROM import_items
567                              JOIN import_records USING (import_record_id)
568                              WHERE import_record_id = ?
569                              ORDER BY import_items_id");
570     $sth->bind_param(1, $import_record_id);
571     $sth->execute();
572     while (my $row = $sth->fetchrow_hashref()) {
573         my $item_marc = MARC::Record->new_from_xml(StripNonXmlChars($row->{'marcxml'}), 'UTF-8', $row->{'encoding'});
574         # FIXME - duplicate barcode check needs to become part of AddItemFromMarc()
575         my $item = TransformMarcToKoha($dbh, $item_marc);
576         my $duplicate_barcode = exists($item->{'barcode'}) && GetItemnumberFromBarcode($item->{'barcode'});
577         if ($duplicate_barcode) {
578             my $updsth = $dbh->prepare("UPDATE import_items SET status = ?, import_error = ? WHERE import_items_id = ?");
579             $updsth->bind_param(1, 'error');
580             $updsth->bind_param(2, 'duplicate item barcode');
581             $updsth->bind_param(3, $row->{'import_items_id'});
582             $updsth->execute();
583             $num_items_errored++;
584         } else {
585             my ($item_biblionumber, $biblioitemnumber, $itemnumber) = AddItemFromMarc($item_marc, $biblionumber);
586             my $updsth = $dbh->prepare("UPDATE import_items SET status = ?, itemnumber = ? WHERE import_items_id = ?");
587             $updsth->bind_param(1, 'imported');
588             $updsth->bind_param(2, $itemnumber);
589             $updsth->bind_param(3, $row->{'import_items_id'});
590             $updsth->execute();
591             $updsth->finish();
592             $num_items_added++;
593         }
594     }
595     $sth->finish();
596     return ($num_items_added, $num_items_errored);
597 }
598
599 =head2 BatchRevertBibRecords
600
601 =over 4
602
603 my ($num_deleted, $num_errors, $num_reverted, $num_items_deleted, $num_ignored) = BatchRevertBibRecords($batch_id);
604
605 =back
606
607 =cut
608
609 sub BatchRevertBibRecords {
610     my $batch_id = shift;
611
612     my $num_deleted = 0;
613     my $num_errors = 0;
614     my $num_reverted = 0;
615     my $num_items_deleted = 0;
616     my $num_ignored = 0;
617     # commit (i.e., save, all records in the batch)
618     # FIXME biblio only at the moment
619     SetImportBatchStatus('reverting');
620     my $overlay_action = GetImportBatchOverlayAction($batch_id);
621     my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
622     my $dbh = C4::Context->dbh;
623     my $sth = $dbh->prepare("SELECT import_record_id, status, overlay_status, marcxml_old, encoding, matched_biblionumber
624                              FROM import_records
625                              JOIN import_biblios USING (import_record_id)
626                              WHERE import_batch_id = ?");
627     $sth->execute($batch_id);
628     while (my $rowref = $sth->fetchrow_hashref) {
629         if ($rowref->{'status'} eq 'error' or $rowref->{'status'} eq 'reverted') {
630             $num_ignored++;
631             next;
632         }
633
634         my $bib_result = _get_revert_action($overlay_action, $rowref->{'overlay_status'}, $rowref->{'status'});
635
636         if ($bib_result eq 'delete') {
637             $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
638             my $error = DelBiblio($rowref->{'matched_biblionumber'});
639             if (defined $error) {
640                 $num_errors++;
641             } else {
642                 $num_deleted++;
643                 SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
644             }
645         } elsif ($bib_result eq 'restore') {
646             $num_reverted++;
647             my $old_record = MARC::Record->new_from_xml(StripNonXmlChars($rowref->{'marcxml_old'}), 'UTF-8', $rowref->{'encoding'});
648             my $biblionumber = $rowref->{'matched_biblionumber'};
649             my ($count, $oldbiblio) = GetBiblio($biblionumber);
650             $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
651             ModBiblio($old_record, $biblionumber, $oldbiblio->{'frameworkcode'});
652             SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
653         } elsif ($bib_result eq 'ignore') {
654             $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
655             SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
656         }
657     }
658
659     $sth->finish();
660     SetImportBatchStatus($batch_id, 'reverted');
661     return ($num_deleted, $num_errors, $num_reverted, $num_items_deleted, $num_ignored);
662 }
663
664 =head2 BatchRevertItems
665
666 =over 4
667
668 my $num_items_deleted = BatchRevertItems($import_record_id, $biblionumber);
669
670 =back
671
672 =cut
673
674 sub BatchRevertItems {
675     my ($import_record_id, $biblionumber) = @_;
676
677     my $dbh = C4::Context->dbh;
678     my $num_items_deleted = 0;
679
680     my $sth = $dbh->prepare_cached("SELECT import_items_id, itemnumber
681                                    FROM import_items
682                                    JOIN items USING (itemnumber)
683                                    WHERE import_record_id = ?");
684     $sth->bind_param(1, $import_record_id);
685     $sth->execute();
686     while (my $row = $sth->fetchrow_hashref()) {
687         DelItem($dbh, $biblionumber, $row->{'itemnumber'});
688         my $updsth = $dbh->prepare("UPDATE import_items SET status = ? WHERE import_items_id = ?");
689         $updsth->bind_param(1, 'reverted');
690         $updsth->bind_param(2, $row->{'import_items_id'});
691         $updsth->execute();
692         $updsth->finish();
693         $num_items_deleted++;
694     }
695     $sth->finish();
696     return $num_items_deleted;
697 }
698
699 =head2 GetAllImportBatches
700
701 =over 4
702
703 my $results = GetAllImportBatches();
704
705 =back
706
707 Returns a references to an array of hash references corresponding
708 to all import_batches rows (of batch_type 'batch'), sorted in 
709 ascending order by import_batch_id.
710
711 =cut
712
713 sub  GetAllImportBatches {
714     my $dbh = C4::Context->dbh;
715     my $sth = $dbh->prepare_cached("SELECT * FROM import_batches
716                                     WHERE batch_type = 'batch'
717                                     ORDER BY import_batch_id ASC");
718
719     my $results = [];
720     $sth->execute();
721     while (my $row = $sth->fetchrow_hashref) {
722         push @$results, $row;
723     }
724     $sth->finish();
725     return $results;
726 }
727
728 =head2 GetImportBatchRangeDesc
729
730 =over 4
731
732 my $results = GetImportBatchRangeDesc($offset, $results_per_group);
733
734 =back
735
736 Returns a reference to an array of hash references corresponding to
737 import_batches rows (sorted in descending order by import_batch_id)
738 start at the given offset.
739
740 =cut
741
742 sub GetImportBatchRangeDesc {
743     my ($offset, $results_per_group) = @_;
744
745     my $dbh = C4::Context->dbh;
746     my $sth = $dbh->prepare_cached("SELECT * FROM import_batches
747                                     WHERE batch_type = 'batch'
748                                     ORDER BY import_batch_id DESC
749                                     LIMIT ? OFFSET ?");
750     $sth->bind_param(1, $results_per_group);
751     $sth->bind_param(2, $offset);
752
753     my $results = [];
754     $sth->execute();
755     while (my $row = $sth->fetchrow_hashref) {
756         push @$results, $row;
757     }
758     $sth->finish();
759     return $results;
760 }
761
762 =head2 GetItemNumbersFromImportBatch
763
764 =over 4
765 =back
766 =cut
767
768 sub GetItemNumbersFromImportBatch {
769         my ($batch_id) = @_;
770         my $dbh = C4::Context->dbh;
771         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=?");
772         $sth->execute($batch_id);
773         my @items ;
774         while ( my ($itm) = $sth->fetchrow_array ) {
775                 push @items, $itm;
776         }
777         return @items;
778 }
779
780 =head2 GetNumberOfImportBatches 
781
782 =over 4
783
784 my $count = GetNumberOfImportBatches();
785
786 =back
787
788 =cut
789
790 sub GetNumberOfNonZ3950ImportBatches {
791     my $dbh = C4::Context->dbh;
792     my $sth = $dbh->prepare("SELECT COUNT(*) FROM import_batches WHERE batch_type='batch'");
793     $sth->execute();
794     my ($count) = $sth->fetchrow_array();
795     $sth->finish();
796     return $count;
797 }
798
799 =head2 GetImportBibliosRange
800
801 =over 4
802
803 my $results = GetImportBibliosRange($batch_id, $offset, $results_per_group);
804
805 =back
806
807 Returns a reference to an array of hash references corresponding to
808 import_biblios/import_records rows for a given batch
809 starting at the given offset.
810
811 =cut
812
813 sub GetImportBibliosRange {
814     my ($batch_id, $offset, $results_per_group) = @_;
815
816     my $dbh = C4::Context->dbh;
817     my $sth = $dbh->prepare_cached("SELECT title, author, isbn, issn, import_record_id, record_sequence,
818                                            status, overlay_status
819                                     FROM   import_records
820                                     JOIN   import_biblios USING (import_record_id)
821                                     WHERE  import_batch_id = ?
822                                     ORDER BY import_record_id LIMIT ? OFFSET ?");
823     $sth->bind_param(1, $batch_id);
824     $sth->bind_param(2, $results_per_group);
825     $sth->bind_param(3, $offset);
826     my $results = [];
827     $sth->execute();
828     while (my $row = $sth->fetchrow_hashref) {
829         push @$results, $row;
830     }
831     $sth->finish();
832     return $results;
833
834 }
835
836 =head2 GetBestRecordMatch
837
838 =over 4
839
840 my $record_id = GetBestRecordMatch($import_record_id);
841
842 =back
843
844 =cut
845
846 sub GetBestRecordMatch {
847     my ($import_record_id) = @_;
848
849     my $dbh = C4::Context->dbh;
850     my $sth = $dbh->prepare("SELECT candidate_match_id
851                              FROM   import_record_matches
852                              WHERE  import_record_id = ?
853                              ORDER BY score DESC, candidate_match_id DESC");
854     $sth->execute($import_record_id);
855     my ($record_id) = $sth->fetchrow_array();
856     $sth->finish();
857     return $record_id;
858 }
859
860 =head2 GetImportBatchStatus
861
862 =over 4
863
864 my $status = GetImportBatchStatus($batch_id);
865
866 =back
867
868 =cut
869
870 sub GetImportBatchStatus {
871     my ($batch_id) = @_;
872
873     my $dbh = C4::Context->dbh;
874     my $sth = $dbh->prepare("SELECT import_status FROM import_batches WHERE batch_id = ?");
875     $sth->execute($batch_id);
876     my ($status) = $sth->fetchrow_array();
877     $sth->finish();
878     return;
879
880 }
881
882
883 =head2 SetImportBatchStatus
884
885 =over 4
886
887 SetImportBatchStatus($batch_id, $new_status);
888
889 =back
890
891 =cut
892
893 sub SetImportBatchStatus {
894     my ($batch_id, $new_status) = @_;
895
896     my $dbh = C4::Context->dbh;
897     my $sth = $dbh->prepare("UPDATE import_batches SET import_status = ? WHERE import_batch_id = ?");
898     $sth->execute($new_status, $batch_id);
899     $sth->finish();
900
901 }
902
903 =head2 GetImportBatchOverlayAction
904
905 =over 4
906
907 my $overlay_action = GetImportBatchOverlayAction($batch_id);
908
909 =back
910
911 =cut
912
913 sub GetImportBatchOverlayAction {
914     my ($batch_id) = @_;
915
916     my $dbh = C4::Context->dbh;
917     my $sth = $dbh->prepare("SELECT overlay_action FROM import_batches WHERE import_batch_id = ?");
918     $sth->execute($batch_id);
919     my ($overlay_action) = $sth->fetchrow_array();
920     $sth->finish();
921     return $overlay_action;
922
923 }
924
925
926 =head2 SetImportBatchOverlayAction
927
928 =over 4
929
930 SetImportBatchOverlayAction($batch_id, $new_overlay_action);
931
932 =back
933
934 =cut
935
936 sub SetImportBatchOverlayAction {
937     my ($batch_id, $new_overlay_action) = @_;
938
939     my $dbh = C4::Context->dbh;
940     my $sth = $dbh->prepare("UPDATE import_batches SET overlay_action = ? WHERE import_batch_id = ?");
941     $sth->execute($new_overlay_action, $batch_id);
942     $sth->finish();
943
944 }
945
946 =head2 GetImportBatchNoMatchAction
947
948 =over 4
949
950 my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
951
952 =back
953
954 =cut
955
956 sub GetImportBatchNoMatchAction {
957     my ($batch_id) = @_;
958
959     my $dbh = C4::Context->dbh;
960     my $sth = $dbh->prepare("SELECT nomatch_action FROM import_batches WHERE import_batch_id = ?");
961     $sth->execute($batch_id);
962     my ($nomatch_action) = $sth->fetchrow_array();
963     $sth->finish();
964     return $nomatch_action;
965
966 }
967
968
969 =head2 SetImportBatchNoMatchAction
970
971 =over 4
972
973 SetImportBatchNoMatchAction($batch_id, $new_nomatch_action);
974
975 =back
976
977 =cut
978
979 sub SetImportBatchNoMatchAction {
980     my ($batch_id, $new_nomatch_action) = @_;
981
982     my $dbh = C4::Context->dbh;
983     my $sth = $dbh->prepare("UPDATE import_batches SET nomatch_action = ? WHERE import_batch_id = ?");
984     $sth->execute($new_nomatch_action, $batch_id);
985     $sth->finish();
986
987 }
988
989 =head2 GetImportBatchItemAction
990
991 =over 4
992
993 my $item_action = GetImportBatchItemAction($batch_id);
994
995 =back
996
997 =cut
998
999 sub GetImportBatchItemAction {
1000     my ($batch_id) = @_;
1001
1002     my $dbh = C4::Context->dbh;
1003     my $sth = $dbh->prepare("SELECT item_action FROM import_batches WHERE import_batch_id = ?");
1004     $sth->execute($batch_id);
1005     my ($item_action) = $sth->fetchrow_array();
1006     $sth->finish();
1007     return $item_action;
1008
1009 }
1010
1011
1012 =head2 SetImportBatchItemAction
1013
1014 =over 4
1015
1016 SetImportBatchItemAction($batch_id, $new_item_action);
1017
1018 =back
1019
1020 =cut
1021
1022 sub SetImportBatchItemAction {
1023     my ($batch_id, $new_item_action) = @_;
1024
1025     my $dbh = C4::Context->dbh;
1026     my $sth = $dbh->prepare("UPDATE import_batches SET item_action = ? WHERE import_batch_id = ?");
1027     $sth->execute($new_item_action, $batch_id);
1028     $sth->finish();
1029
1030 }
1031
1032 =head2 GetImportBatchMatcher
1033
1034 =over 4
1035
1036 my $matcher_id = GetImportBatchMatcher($batch_id);
1037
1038 =back
1039
1040 =cut
1041
1042 sub GetImportBatchMatcher {
1043     my ($batch_id) = @_;
1044
1045     my $dbh = C4::Context->dbh;
1046     my $sth = $dbh->prepare("SELECT matcher_id FROM import_batches WHERE import_batch_id = ?");
1047     $sth->execute($batch_id);
1048     my ($matcher_id) = $sth->fetchrow_array();
1049     $sth->finish();
1050     return $matcher_id;
1051
1052 }
1053
1054
1055 =head2 SetImportBatchMatcher
1056
1057 =over 4
1058
1059 SetImportBatchMatcher($batch_id, $new_matcher_id);
1060
1061 =back
1062
1063 =cut
1064
1065 sub SetImportBatchMatcher {
1066     my ($batch_id, $new_matcher_id) = @_;
1067
1068     my $dbh = C4::Context->dbh;
1069     my $sth = $dbh->prepare("UPDATE import_batches SET matcher_id = ? WHERE import_batch_id = ?");
1070     $sth->execute($new_matcher_id, $batch_id);
1071     $sth->finish();
1072
1073 }
1074
1075 =head2 GetImportRecordOverlayStatus
1076
1077 =over 4
1078
1079 my $overlay_status = GetImportRecordOverlayStatus($import_record_id);
1080
1081 =back
1082
1083 =cut
1084
1085 sub GetImportRecordOverlayStatus {
1086     my ($import_record_id) = @_;
1087
1088     my $dbh = C4::Context->dbh;
1089     my $sth = $dbh->prepare("SELECT overlay_status FROM import_records WHERE import_record_id = ?");
1090     $sth->execute($import_record_id);
1091     my ($overlay_status) = $sth->fetchrow_array();
1092     $sth->finish();
1093     return $overlay_status;
1094
1095 }
1096
1097
1098 =head2 SetImportRecordOverlayStatus
1099
1100 =over 4
1101
1102 SetImportRecordOverlayStatus($import_record_id, $new_overlay_status);
1103
1104 =back
1105
1106 =cut
1107
1108 sub SetImportRecordOverlayStatus {
1109     my ($import_record_id, $new_overlay_status) = @_;
1110
1111     my $dbh = C4::Context->dbh;
1112     my $sth = $dbh->prepare("UPDATE import_records SET overlay_status = ? WHERE import_record_id = ?");
1113     $sth->execute($new_overlay_status, $import_record_id);
1114     $sth->finish();
1115
1116 }
1117
1118 =head2 GetImportRecordStatus
1119
1120 =over 4
1121
1122 my $overlay_status = GetImportRecordStatus($import_record_id);
1123
1124 =back
1125
1126 =cut
1127
1128 sub GetImportRecordStatus {
1129     my ($import_record_id) = @_;
1130
1131     my $dbh = C4::Context->dbh;
1132     my $sth = $dbh->prepare("SELECT status FROM import_records WHERE import_record_id = ?");
1133     $sth->execute($import_record_id);
1134     my ($overlay_status) = $sth->fetchrow_array();
1135     $sth->finish();
1136     return $overlay_status;
1137
1138 }
1139
1140
1141 =head2 SetImportRecordStatus
1142
1143 =over 4
1144
1145 SetImportRecordStatus($import_record_id, $new_overlay_status);
1146
1147 =back
1148
1149 =cut
1150
1151 sub SetImportRecordStatus {
1152     my ($import_record_id, $new_overlay_status) = @_;
1153
1154     my $dbh = C4::Context->dbh;
1155     my $sth = $dbh->prepare("UPDATE import_records SET status = ? WHERE import_record_id = ?");
1156     $sth->execute($new_overlay_status, $import_record_id);
1157     $sth->finish();
1158
1159 }
1160
1161 =head2 GetImportRecordMatches
1162
1163 =over 4
1164
1165 my $results = GetImportRecordMatches($import_record_id, $best_only);
1166
1167 =back
1168
1169 =cut
1170
1171 sub GetImportRecordMatches {
1172     my $import_record_id = shift;
1173     my $best_only = @_ ? shift : 0;
1174
1175     my $dbh = C4::Context->dbh;
1176     # FIXME currently biblio only
1177     my $sth = $dbh->prepare_cached("SELECT title, author, biblionumber, score
1178                                     FROM import_records
1179                                     JOIN import_record_matches USING (import_record_id)
1180                                     JOIN biblio ON (biblionumber = candidate_match_id)
1181                                     WHERE import_record_id = ?
1182                                     ORDER BY score DESC, biblionumber DESC");
1183     $sth->bind_param(1, $import_record_id);
1184     my $results = [];
1185     $sth->execute();
1186     while (my $row = $sth->fetchrow_hashref) {
1187         push @$results, $row;
1188         last if $best_only;
1189     }
1190     $sth->finish();
1191
1192     return $results;
1193     
1194 }
1195
1196
1197 =head2 SetImportRecordMatches
1198
1199 =over 4
1200
1201 SetImportRecordMatches($import_record_id, @matches);
1202
1203 =back
1204
1205 =cut
1206
1207 sub SetImportRecordMatches {
1208     my $import_record_id = shift;
1209     my @matches = @_;
1210
1211     my $dbh = C4::Context->dbh;
1212     my $delsth = $dbh->prepare("DELETE FROM import_record_matches WHERE import_record_id = ?");
1213     $delsth->execute($import_record_id);
1214     $delsth->finish();
1215
1216     my $sth = $dbh->prepare("INSERT INTO import_record_matches (import_record_id, candidate_match_id, score)
1217                                     VALUES (?, ?, ?)");
1218     foreach my $match (@matches) {
1219         $sth->execute($import_record_id, $match->{'record_id'}, $match->{'score'});
1220     }
1221 }
1222
1223
1224 # internal functions
1225
1226 sub _create_import_record {
1227     my ($batch_id, $record_sequence, $marc_record, $record_type, $encoding, $z3950random) = @_;
1228
1229     my $dbh = C4::Context->dbh;
1230     my $sth = $dbh->prepare("INSERT INTO import_records (import_batch_id, record_sequence, marc, marcxml, 
1231                                                          record_type, encoding, z3950random)
1232                                     VALUES (?, ?, ?, ?, ?, ?, ?)");
1233     $sth->execute($batch_id, $record_sequence, $marc_record->as_usmarc(), $marc_record->as_xml(),
1234                   $record_type, $encoding, $z3950random);
1235     my $import_record_id = $dbh->{'mysql_insertid'};
1236     $sth->finish();
1237     return $import_record_id;
1238 }
1239
1240 sub _update_import_record_marc {
1241     my ($import_record_id, $marc_record) = @_;
1242
1243     my $dbh = C4::Context->dbh;
1244     my $sth = $dbh->prepare("UPDATE import_records SET marc = ?, marcxml = ?
1245                              WHERE  import_record_id = ?");
1246     $sth->execute($marc_record->as_usmarc(), $marc_record->as_xml(), $import_record_id);
1247     $sth->finish();
1248 }
1249
1250 sub _add_biblio_fields {
1251     my ($import_record_id, $marc_record) = @_;
1252
1253     my ($title, $author, $isbn, $issn) = _parse_biblio_fields($marc_record);
1254     my $dbh = C4::Context->dbh;
1255     # FIXME no controlnumber, originalsource
1256     # FIXME 2 - should regularize normalization of ISBN wherever it is done
1257     $isbn =~ s/\(.*$//;
1258     $isbn =~ tr/ -_//;  
1259     $isbn = uc $isbn;
1260     my $sth = $dbh->prepare("INSERT INTO import_biblios (import_record_id, title, author, isbn, issn) VALUES (?, ?, ?, ?, ?)");
1261     $sth->execute($import_record_id, $title, $author, $isbn, $issn);
1262     $sth->finish();
1263                 
1264 }
1265
1266 sub _update_biblio_fields {
1267     my ($import_record_id, $marc_record) = @_;
1268
1269     my ($title, $author, $isbn, $issn) = _parse_biblio_fields($marc_record);
1270     my $dbh = C4::Context->dbh;
1271     # FIXME no controlnumber, originalsource
1272     # FIXME 2 - should regularize normalization of ISBN wherever it is done
1273     $isbn =~ s/\(.*$//;
1274     $isbn =~ tr/ -_//;
1275     $isbn = uc $isbn;
1276     my $sth = $dbh->prepare("UPDATE import_biblios SET title = ?, author = ?, isbn = ?, issn = ?
1277                              WHERE  import_record_id = ?");
1278     $sth->execute($title, $author, $isbn, $issn, $import_record_id);
1279     $sth->finish();
1280 }
1281
1282 sub _parse_biblio_fields {
1283     my ($marc_record) = @_;
1284
1285     my $dbh = C4::Context->dbh;
1286     my $bibliofields = TransformMarcToKoha($dbh, $marc_record, '');
1287     return ($bibliofields->{'title'}, $bibliofields->{'author'}, $bibliofields->{'isbn'}, $bibliofields->{'issn'});
1288
1289 }
1290
1291 sub _update_batch_record_counts {
1292     my ($batch_id) = @_;
1293
1294     my $dbh = C4::Context->dbh;
1295     my $sth = $dbh->prepare_cached("UPDATE import_batches SET num_biblios = (
1296                                     SELECT COUNT(*)
1297                                     FROM import_records
1298                                     WHERE import_batch_id = import_batches.import_batch_id
1299                                     AND record_type = 'biblio')
1300                                     WHERE import_batch_id = ?");
1301     $sth->bind_param(1, $batch_id);
1302     $sth->execute();
1303     $sth->finish();
1304     $sth = $dbh->prepare_cached("UPDATE import_batches SET num_items = (
1305                                     SELECT COUNT(*)
1306                                     FROM import_records
1307                                     JOIN import_items USING (import_record_id)
1308                                     WHERE import_batch_id = import_batches.import_batch_id
1309                                     AND record_type = 'biblio')
1310                                     WHERE import_batch_id = ?");
1311     $sth->bind_param(1, $batch_id);
1312     $sth->execute();
1313     $sth->finish();
1314
1315 }
1316
1317 sub _get_commit_action {
1318     my ($overlay_action, $nomatch_action, $item_action, $overlay_status, $import_record_id) = @_;
1319     
1320     my ($bib_result, $bib_match, $item_result);
1321
1322     if ($overlay_status ne 'no_match') {
1323         $bib_match = GetBestRecordMatch($import_record_id);
1324         if ($overlay_action eq 'replace') {
1325             $bib_result  = defined($bib_match) ? 'replace' : 'create_new';
1326         } elsif ($overlay_action eq 'create_new') {
1327             $bib_result  = 'create_new';
1328         } elsif ($overlay_action eq 'ignore') {
1329             $bib_result  = 'ignore';
1330         } 
1331         $item_result = ($item_action eq 'always_add' or $item_action eq 'add_only_for_matches') ? 'create_new' : 'ignore';
1332     } else {
1333         $bib_result = $nomatch_action;
1334         $item_result = ($item_action eq 'always_add' or $item_action eq 'add_only_for_new')     ? 'create_new' : 'ignore';
1335     }
1336
1337     return ($bib_result, $item_result, $bib_match);
1338 }
1339
1340 sub _get_revert_action {
1341     my ($overlay_action, $overlay_status, $status) = @_;
1342
1343     my $bib_result;
1344
1345     if ($status eq 'ignored') {
1346         $bib_result = 'ignore';
1347     } else {
1348         if ($overlay_action eq 'create_new') {
1349             $bib_result = 'delete';
1350         } else {
1351             $bib_result = ($overlay_status eq 'match_applied') ? 'restore' : 'delete';
1352         }
1353     }
1354     return $bib_result;
1355 }
1356
1357 1;
1358 __END__
1359
1360 =head1 AUTHOR
1361
1362 Koha Development Team <info@koha.org>
1363
1364 Galen Charlton <galen.charlton@liblime.com>
1365
1366 =cut