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