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