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