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