Cleanup C4/Stats.pm
[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         my $sth2 = $dbh->prepare_cached("UPDATE import_biblios SET matched_biblionumber = NULL WHERE import_record_id = ?");
658         $sth2->execute($rowref->{'import_record_id'});
659     }
660
661     $sth->finish();
662     SetImportBatchStatus($batch_id, 'reverted');
663     return ($num_deleted, $num_errors, $num_reverted, $num_items_deleted, $num_ignored);
664 }
665
666 =head2 BatchRevertItems
667
668 =over 4
669
670 my $num_items_deleted = BatchRevertItems($import_record_id, $biblionumber);
671
672 =back
673
674 =cut
675
676 sub BatchRevertItems {
677     my ($import_record_id, $biblionumber) = @_;
678
679     my $dbh = C4::Context->dbh;
680     my $num_items_deleted = 0;
681
682     my $sth = $dbh->prepare_cached("SELECT import_items_id, itemnumber
683                                    FROM import_items
684                                    JOIN items USING (itemnumber)
685                                    WHERE import_record_id = ?");
686     $sth->bind_param(1, $import_record_id);
687     $sth->execute();
688     while (my $row = $sth->fetchrow_hashref()) {
689         DelItem($dbh, $biblionumber, $row->{'itemnumber'});
690         my $updsth = $dbh->prepare("UPDATE import_items SET status = ? WHERE import_items_id = ?");
691         $updsth->bind_param(1, 'reverted');
692         $updsth->bind_param(2, $row->{'import_items_id'});
693         $updsth->execute();
694         $updsth->finish();
695         $num_items_deleted++;
696     }
697     $sth->finish();
698     return $num_items_deleted;
699 }
700
701 =head2 GetAllImportBatches
702
703 =over 4
704
705 my $results = GetAllImportBatches();
706
707 =back
708
709 Returns a references to an array of hash references corresponding
710 to all import_batches rows (of batch_type 'batch'), sorted in 
711 ascending order by import_batch_id.
712
713 =cut
714
715 sub  GetAllImportBatches {
716     my $dbh = C4::Context->dbh;
717     my $sth = $dbh->prepare_cached("SELECT * FROM import_batches
718                                     WHERE batch_type = 'batch'
719                                     ORDER BY import_batch_id ASC");
720
721     my $results = [];
722     $sth->execute();
723     while (my $row = $sth->fetchrow_hashref) {
724         push @$results, $row;
725     }
726     $sth->finish();
727     return $results;
728 }
729
730 =head2 GetImportBatchRangeDesc
731
732 =over 4
733
734 my $results = GetImportBatchRangeDesc($offset, $results_per_group);
735
736 =back
737
738 Returns a reference to an array of hash references corresponding to
739 import_batches rows (sorted in descending order by import_batch_id)
740 start at the given offset.
741
742 =cut
743
744 sub GetImportBatchRangeDesc {
745     my ($offset, $results_per_group) = @_;
746
747     my $dbh = C4::Context->dbh;
748     my $sth = $dbh->prepare_cached("SELECT * FROM import_batches
749                                     WHERE batch_type = 'batch'
750                                     ORDER BY import_batch_id DESC
751                                     LIMIT ? OFFSET ?");
752     $sth->bind_param(1, $results_per_group);
753     $sth->bind_param(2, $offset);
754
755     my $results = [];
756     $sth->execute();
757     while (my $row = $sth->fetchrow_hashref) {
758         push @$results, $row;
759     }
760     $sth->finish();
761     return $results;
762 }
763
764 =head2 GetItemNumbersFromImportBatch
765
766 =cut
767
768 sub GetItemNumbersFromImportBatch {
769         my ($batch_id) = @_;
770         my $dbh = C4::Context->dbh;
771         my $sth = $dbh->prepare("select itemnumber from import_batches,import_records,import_items where import_batches.import_batch_id=import_records.import_batch_id and import_records.import_record_id=import_items.import_record_id and import_batches.import_batch_id=?");
772         $sth->execute($batch_id);
773         my @items ;
774         while ( my ($itm) = $sth->fetchrow_array ) {
775                 push @items, $itm;
776         }
777         return @items;
778 }
779
780 =head2 GetNumberOfImportBatches 
781
782 =over 4
783
784 my $count = GetNumberOfImportBatches();
785
786 =back
787
788 =cut
789
790 sub GetNumberOfNonZ3950ImportBatches {
791     my $dbh = C4::Context->dbh;
792     my $sth = $dbh->prepare("SELECT COUNT(*) FROM import_batches WHERE batch_type='batch'");
793     $sth->execute();
794     my ($count) = $sth->fetchrow_array();
795     $sth->finish();
796     return $count;
797 }
798
799 =head2 GetImportBibliosRange
800
801 =over 4
802
803 my $results = GetImportBibliosRange($batch_id, $offset, $results_per_group);
804
805 =back
806
807 Returns a reference to an array of hash references corresponding to
808 import_biblios/import_records rows for a given batch
809 starting at the given offset.
810
811 =cut
812
813 sub GetImportBibliosRange {
814     my ($batch_id, $offset, $results_per_group) = @_;
815
816     my $dbh = C4::Context->dbh;
817     my $sth = $dbh->prepare_cached("SELECT title, author, isbn, issn, import_record_id, record_sequence,
818                                            matched_biblionumber, status, overlay_status
819                                     FROM   import_records
820                                     JOIN   import_biblios USING (import_record_id)
821                                     WHERE  import_batch_id = ?
822                                     ORDER BY import_record_id LIMIT ? OFFSET ?");
823     $sth->bind_param(1, $batch_id);
824     $sth->bind_param(2, $results_per_group);
825     $sth->bind_param(3, $offset);
826     my $results = [];
827     $sth->execute();
828     while (my $row = $sth->fetchrow_hashref) {
829         push @$results, $row;
830     }
831     $sth->finish();
832     return $results;
833
834 }
835
836 =head2 GetBestRecordMatch
837
838 =over 4
839
840 my $record_id = GetBestRecordMatch($import_record_id);
841
842 =back
843
844 =cut
845
846 sub GetBestRecordMatch {
847     my ($import_record_id) = @_;
848
849     my $dbh = C4::Context->dbh;
850     my $sth = $dbh->prepare("SELECT candidate_match_id
851                              FROM   import_record_matches
852                              WHERE  import_record_id = ?
853                              ORDER BY score DESC, candidate_match_id DESC");
854     $sth->execute($import_record_id);
855     my ($record_id) = $sth->fetchrow_array();
856     $sth->finish();
857     return $record_id;
858 }
859
860 =head2 GetImportBatchStatus
861
862 =over 4
863
864 my $status = GetImportBatchStatus($batch_id);
865
866 =back
867
868 =cut
869
870 sub GetImportBatchStatus {
871     my ($batch_id) = @_;
872
873     my $dbh = C4::Context->dbh;
874     my $sth = $dbh->prepare("SELECT import_status FROM import_batches WHERE import_batch_id = ?");
875     $sth->execute($batch_id);
876     my ($status) = $sth->fetchrow_array();
877     $sth->finish();
878     return $status;
879
880 }
881
882 =head2 SetImportBatchStatus
883
884 =over 4
885
886 SetImportBatchStatus($batch_id, $new_status);
887
888 =back
889
890 =cut
891
892 sub SetImportBatchStatus {
893     my ($batch_id, $new_status) = @_;
894
895     my $dbh = C4::Context->dbh;
896     my $sth = $dbh->prepare("UPDATE import_batches SET import_status = ? WHERE import_batch_id = ?");
897     $sth->execute($new_status, $batch_id);
898     $sth->finish();
899
900 }
901
902 =head2 GetImportBatchOverlayAction
903
904 =over 4
905
906 my $overlay_action = GetImportBatchOverlayAction($batch_id);
907
908 =back
909
910 =cut
911
912 sub GetImportBatchOverlayAction {
913     my ($batch_id) = @_;
914
915     my $dbh = C4::Context->dbh;
916     my $sth = $dbh->prepare("SELECT overlay_action FROM import_batches WHERE import_batch_id = ?");
917     $sth->execute($batch_id);
918     my ($overlay_action) = $sth->fetchrow_array();
919     $sth->finish();
920     return $overlay_action;
921
922 }
923
924
925 =head2 SetImportBatchOverlayAction
926
927 =over 4
928
929 SetImportBatchOverlayAction($batch_id, $new_overlay_action);
930
931 =back
932
933 =cut
934
935 sub SetImportBatchOverlayAction {
936     my ($batch_id, $new_overlay_action) = @_;
937
938     my $dbh = C4::Context->dbh;
939     my $sth = $dbh->prepare("UPDATE import_batches SET overlay_action = ? WHERE import_batch_id = ?");
940     $sth->execute($new_overlay_action, $batch_id);
941     $sth->finish();
942
943 }
944
945 =head2 GetImportBatchNoMatchAction
946
947 =over 4
948
949 my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
950
951 =back
952
953 =cut
954
955 sub GetImportBatchNoMatchAction {
956     my ($batch_id) = @_;
957
958     my $dbh = C4::Context->dbh;
959     my $sth = $dbh->prepare("SELECT nomatch_action FROM import_batches WHERE import_batch_id = ?");
960     $sth->execute($batch_id);
961     my ($nomatch_action) = $sth->fetchrow_array();
962     $sth->finish();
963     return $nomatch_action;
964
965 }
966
967
968 =head2 SetImportBatchNoMatchAction
969
970 =over 4
971
972 SetImportBatchNoMatchAction($batch_id, $new_nomatch_action);
973
974 =back
975
976 =cut
977
978 sub SetImportBatchNoMatchAction {
979     my ($batch_id, $new_nomatch_action) = @_;
980
981     my $dbh = C4::Context->dbh;
982     my $sth = $dbh->prepare("UPDATE import_batches SET nomatch_action = ? WHERE import_batch_id = ?");
983     $sth->execute($new_nomatch_action, $batch_id);
984     $sth->finish();
985
986 }
987
988 =head2 GetImportBatchItemAction
989
990 =over 4
991
992 my $item_action = GetImportBatchItemAction($batch_id);
993
994 =back
995
996 =cut
997
998 sub GetImportBatchItemAction {
999     my ($batch_id) = @_;
1000
1001     my $dbh = C4::Context->dbh;
1002     my $sth = $dbh->prepare("SELECT item_action FROM import_batches WHERE import_batch_id = ?");
1003     $sth->execute($batch_id);
1004     my ($item_action) = $sth->fetchrow_array();
1005     $sth->finish();
1006     return $item_action;
1007
1008 }
1009
1010
1011 =head2 SetImportBatchItemAction
1012
1013 =over 4
1014
1015 SetImportBatchItemAction($batch_id, $new_item_action);
1016
1017 =back
1018
1019 =cut
1020
1021 sub SetImportBatchItemAction {
1022     my ($batch_id, $new_item_action) = @_;
1023
1024     my $dbh = C4::Context->dbh;
1025     my $sth = $dbh->prepare("UPDATE import_batches SET item_action = ? WHERE import_batch_id = ?");
1026     $sth->execute($new_item_action, $batch_id);
1027     $sth->finish();
1028
1029 }
1030
1031 =head2 GetImportBatchMatcher
1032
1033 =over 4
1034
1035 my $matcher_id = GetImportBatchMatcher($batch_id);
1036
1037 =back
1038
1039 =cut
1040
1041 sub GetImportBatchMatcher {
1042     my ($batch_id) = @_;
1043
1044     my $dbh = C4::Context->dbh;
1045     my $sth = $dbh->prepare("SELECT matcher_id FROM import_batches WHERE import_batch_id = ?");
1046     $sth->execute($batch_id);
1047     my ($matcher_id) = $sth->fetchrow_array();
1048     $sth->finish();
1049     return $matcher_id;
1050
1051 }
1052
1053
1054 =head2 SetImportBatchMatcher
1055
1056 =over 4
1057
1058 SetImportBatchMatcher($batch_id, $new_matcher_id);
1059
1060 =back
1061
1062 =cut
1063
1064 sub SetImportBatchMatcher {
1065     my ($batch_id, $new_matcher_id) = @_;
1066
1067     my $dbh = C4::Context->dbh;
1068     my $sth = $dbh->prepare("UPDATE import_batches SET matcher_id = ? WHERE import_batch_id = ?");
1069     $sth->execute($new_matcher_id, $batch_id);
1070     $sth->finish();
1071
1072 }
1073
1074 =head2 GetImportRecordOverlayStatus
1075
1076 =over 4
1077
1078 my $overlay_status = GetImportRecordOverlayStatus($import_record_id);
1079
1080 =back
1081
1082 =cut
1083
1084 sub GetImportRecordOverlayStatus {
1085     my ($import_record_id) = @_;
1086
1087     my $dbh = C4::Context->dbh;
1088     my $sth = $dbh->prepare("SELECT overlay_status FROM import_records WHERE import_record_id = ?");
1089     $sth->execute($import_record_id);
1090     my ($overlay_status) = $sth->fetchrow_array();
1091     $sth->finish();
1092     return $overlay_status;
1093
1094 }
1095
1096
1097 =head2 SetImportRecordOverlayStatus
1098
1099 =over 4
1100
1101 SetImportRecordOverlayStatus($import_record_id, $new_overlay_status);
1102
1103 =back
1104
1105 =cut
1106
1107 sub SetImportRecordOverlayStatus {
1108     my ($import_record_id, $new_overlay_status) = @_;
1109
1110     my $dbh = C4::Context->dbh;
1111     my $sth = $dbh->prepare("UPDATE import_records SET overlay_status = ? WHERE import_record_id = ?");
1112     $sth->execute($new_overlay_status, $import_record_id);
1113     $sth->finish();
1114
1115 }
1116
1117 =head2 GetImportRecordStatus
1118
1119 =over 4
1120
1121 my $overlay_status = GetImportRecordStatus($import_record_id);
1122
1123 =back
1124
1125 =cut
1126
1127 sub GetImportRecordStatus {
1128     my ($import_record_id) = @_;
1129
1130     my $dbh = C4::Context->dbh;
1131     my $sth = $dbh->prepare("SELECT status FROM import_records WHERE import_record_id = ?");
1132     $sth->execute($import_record_id);
1133     my ($overlay_status) = $sth->fetchrow_array();
1134     $sth->finish();
1135     return $overlay_status;
1136
1137 }
1138
1139
1140 =head2 SetImportRecordStatus
1141
1142 =over 4
1143
1144 SetImportRecordStatus($import_record_id, $new_overlay_status);
1145
1146 =back
1147
1148 =cut
1149
1150 sub SetImportRecordStatus {
1151     my ($import_record_id, $new_overlay_status) = @_;
1152
1153     my $dbh = C4::Context->dbh;
1154     my $sth = $dbh->prepare("UPDATE import_records SET status = ? WHERE import_record_id = ?");
1155     $sth->execute($new_overlay_status, $import_record_id);
1156     $sth->finish();
1157
1158 }
1159
1160 =head2 GetImportRecordMatches
1161
1162 =over 4
1163
1164 my $results = GetImportRecordMatches($import_record_id, $best_only);
1165
1166 =back
1167
1168 =cut
1169
1170 sub GetImportRecordMatches {
1171     my $import_record_id = shift;
1172     my $best_only = @_ ? shift : 0;
1173
1174     my $dbh = C4::Context->dbh;
1175     # FIXME currently biblio only
1176     my $sth = $dbh->prepare_cached("SELECT title, author, biblionumber, score
1177                                     FROM import_records
1178                                     JOIN import_record_matches USING (import_record_id)
1179                                     JOIN biblio ON (biblionumber = candidate_match_id)
1180                                     WHERE import_record_id = ?
1181                                     ORDER BY score DESC, biblionumber DESC");
1182     $sth->bind_param(1, $import_record_id);
1183     my $results = [];
1184     $sth->execute();
1185     while (my $row = $sth->fetchrow_hashref) {
1186         push @$results, $row;
1187         last if $best_only;
1188     }
1189     $sth->finish();
1190
1191     return $results;
1192     
1193 }
1194
1195
1196 =head2 SetImportRecordMatches
1197
1198 =over 4
1199
1200 SetImportRecordMatches($import_record_id, @matches);
1201
1202 =back
1203
1204 =cut
1205
1206 sub SetImportRecordMatches {
1207     my $import_record_id = shift;
1208     my @matches = @_;
1209
1210     my $dbh = C4::Context->dbh;
1211     my $delsth = $dbh->prepare("DELETE FROM import_record_matches WHERE import_record_id = ?");
1212     $delsth->execute($import_record_id);
1213     $delsth->finish();
1214
1215     my $sth = $dbh->prepare("INSERT INTO import_record_matches (import_record_id, candidate_match_id, score)
1216                                     VALUES (?, ?, ?)");
1217     foreach my $match (@matches) {
1218         $sth->execute($import_record_id, $match->{'record_id'}, $match->{'score'});
1219     }
1220 }
1221
1222
1223 # internal functions
1224
1225 sub _create_import_record {
1226     my ($batch_id, $record_sequence, $marc_record, $record_type, $encoding, $z3950random) = @_;
1227
1228     my $dbh = C4::Context->dbh;
1229     my $sth = $dbh->prepare("INSERT INTO import_records (import_batch_id, record_sequence, marc, marcxml, 
1230                                                          record_type, encoding, z3950random)
1231                                     VALUES (?, ?, ?, ?, ?, ?, ?)");
1232     $sth->execute($batch_id, $record_sequence, $marc_record->as_usmarc(), $marc_record->as_xml(),
1233                   $record_type, $encoding, $z3950random);
1234     my $import_record_id = $dbh->{'mysql_insertid'};
1235     $sth->finish();
1236     return $import_record_id;
1237 }
1238
1239 sub _update_import_record_marc {
1240     my ($import_record_id, $marc_record) = @_;
1241
1242     my $dbh = C4::Context->dbh;
1243     my $sth = $dbh->prepare("UPDATE import_records SET marc = ?, marcxml = ?
1244                              WHERE  import_record_id = ?");
1245     $sth->execute($marc_record->as_usmarc(), $marc_record->as_xml(), $import_record_id);
1246     $sth->finish();
1247 }
1248
1249 sub _add_biblio_fields {
1250     my ($import_record_id, $marc_record) = @_;
1251
1252     my ($title, $author, $isbn, $issn) = _parse_biblio_fields($marc_record);
1253     my $dbh = C4::Context->dbh;
1254     # FIXME no controlnumber, originalsource
1255     # FIXME 2 - should regularize normalization of ISBN wherever it is done
1256     $isbn =~ s/\(.*$//;
1257     $isbn =~ tr/ -_//;  
1258     $isbn = uc $isbn;
1259     my $sth = $dbh->prepare("INSERT INTO import_biblios (import_record_id, title, author, isbn, issn) VALUES (?, ?, ?, ?, ?)");
1260     $sth->execute($import_record_id, $title, $author, $isbn, $issn);
1261     $sth->finish();
1262                 
1263 }
1264
1265 sub _update_biblio_fields {
1266     my ($import_record_id, $marc_record) = @_;
1267
1268     my ($title, $author, $isbn, $issn) = _parse_biblio_fields($marc_record);
1269     my $dbh = C4::Context->dbh;
1270     # FIXME no controlnumber, originalsource
1271     # FIXME 2 - should regularize normalization of ISBN wherever it is done
1272     $isbn =~ s/\(.*$//;
1273     $isbn =~ tr/ -_//;
1274     $isbn = uc $isbn;
1275     my $sth = $dbh->prepare("UPDATE import_biblios SET title = ?, author = ?, isbn = ?, issn = ?
1276                              WHERE  import_record_id = ?");
1277     $sth->execute($title, $author, $isbn, $issn, $import_record_id);
1278     $sth->finish();
1279 }
1280
1281 sub _parse_biblio_fields {
1282     my ($marc_record) = @_;
1283
1284     my $dbh = C4::Context->dbh;
1285     my $bibliofields = TransformMarcToKoha($dbh, $marc_record, '');
1286     return ($bibliofields->{'title'}, $bibliofields->{'author'}, $bibliofields->{'isbn'}, $bibliofields->{'issn'});
1287
1288 }
1289
1290 sub _update_batch_record_counts {
1291     my ($batch_id) = @_;
1292
1293     my $dbh = C4::Context->dbh;
1294     my $sth = $dbh->prepare_cached("UPDATE import_batches SET num_biblios = (
1295                                     SELECT COUNT(*)
1296                                     FROM import_records
1297                                     WHERE import_batch_id = import_batches.import_batch_id
1298                                     AND record_type = 'biblio')
1299                                     WHERE import_batch_id = ?");
1300     $sth->bind_param(1, $batch_id);
1301     $sth->execute();
1302     $sth->finish();
1303     $sth = $dbh->prepare_cached("UPDATE import_batches SET num_items = (
1304                                     SELECT COUNT(*)
1305                                     FROM import_records
1306                                     JOIN import_items USING (import_record_id)
1307                                     WHERE import_batch_id = import_batches.import_batch_id
1308                                     AND record_type = 'biblio')
1309                                     WHERE import_batch_id = ?");
1310     $sth->bind_param(1, $batch_id);
1311     $sth->execute();
1312     $sth->finish();
1313
1314 }
1315
1316 sub _get_commit_action {
1317     my ($overlay_action, $nomatch_action, $item_action, $overlay_status, $import_record_id) = @_;
1318     
1319     my ($bib_result, $bib_match, $item_result);
1320
1321     if ($overlay_status ne 'no_match') {
1322         $bib_match = GetBestRecordMatch($import_record_id);
1323         if ($overlay_action eq 'replace') {
1324             $bib_result  = defined($bib_match) ? 'replace' : 'create_new';
1325         } elsif ($overlay_action eq 'create_new') {
1326             $bib_result  = 'create_new';
1327         } elsif ($overlay_action eq 'ignore') {
1328             $bib_result  = 'ignore';
1329         } 
1330         $item_result = ($item_action eq 'always_add' or $item_action eq 'add_only_for_matches') ? 'create_new' : 'ignore';
1331     } else {
1332         $bib_result = $nomatch_action;
1333         $item_result = ($item_action eq 'always_add' or $item_action eq 'add_only_for_new')     ? 'create_new' : 'ignore';
1334     }
1335
1336     return ($bib_result, $item_result, $bib_match);
1337 }
1338
1339 sub _get_revert_action {
1340     my ($overlay_action, $overlay_status, $status) = @_;
1341
1342     my $bib_result;
1343
1344     if ($status eq 'ignored') {
1345         $bib_result = 'ignore';
1346     } else {
1347         if ($overlay_action eq 'create_new') {
1348             $bib_result = 'delete';
1349         } else {
1350             $bib_result = ($overlay_status eq 'match_applied') ? 'restore' : 'delete';
1351         }
1352     }
1353     return $bib_result;
1354 }
1355
1356 1;
1357 __END__
1358
1359 =head1 AUTHOR
1360
1361 Koha Development Team <info@koha.org>
1362
1363 Galen Charlton <galen.charlton@liblime.com>
1364
1365 =cut