Bug 2094: cleanup of lost items report
[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         }
473
474         my $marc_record = MARC::Record->new_from_usmarc($rowref->{'marc'});
475
476         # remove any item tags - rely on BatchCommitItems
477         my ($item_tag,$item_subfield) = &GetMarcFromKohaField("items.itemnumber",'');
478         foreach my $item_field ($marc_record->field($item_tag)) {
479             $marc_record->delete_field($item_field);
480         }
481
482         # decide what what to do with the bib and item records
483         my ($bib_result, $item_result, $bib_match) = 
484             _get_commit_action($overlay_action, $nomatch_action, $item_action, 
485                                $rowref->{'overlay_status'}, $rowref->{'import_record_id'});
486
487         if ($bib_result eq 'create_new') {
488             $num_added++;
489             my ($biblionumber, $biblioitemnumber) = AddBiblio($marc_record, '');
490             my $sth = $dbh->prepare_cached("UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?");
491             $sth->execute($biblionumber, $rowref->{'import_record_id'});
492             $sth->finish();
493             if ($item_result eq 'create_new') {
494                 my ($bib_items_added, $bib_items_errored) = BatchCommitItems($rowref->{'import_record_id'}, $biblionumber);
495                 $num_items_added += $bib_items_added;
496                 $num_items_errored += $bib_items_errored;
497             }
498             SetImportRecordStatus($rowref->{'import_record_id'}, 'imported');
499         } elsif ($bib_result eq 'replace') {
500             $num_updated++;
501             my $biblionumber = $bib_match;
502             my ($count, $oldbiblio) = GetBiblio($biblionumber);
503             my $oldxml = GetXmlBiblio($biblionumber);
504
505             # remove item fields so that they don't get
506             # added again if record is reverted
507             my $old_marc = MARC::Record->new_from_xml(StripNonXmlChars($oldxml), 'UTF-8', $rowref->{'encoding'});
508             foreach my $item_field ($old_marc->field($item_tag)) {
509                 $old_marc->delete_field($item_field);
510             }
511
512             ModBiblio($marc_record, $biblionumber, $oldbiblio->{'frameworkcode'});
513             my $sth = $dbh->prepare_cached("UPDATE import_records SET marcxml_old = ? WHERE import_record_id = ?");
514             $sth->execute($old_marc->as_xml(), $rowref->{'import_record_id'});
515             $sth->finish();
516             my $sth2 = $dbh->prepare_cached("UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?");
517             $sth2->execute($biblionumber, $rowref->{'import_record_id'});
518             $sth2->finish();
519             if ($item_result eq 'create_new') {
520                 my ($bib_items_added, $bib_items_errored) = BatchCommitItems($rowref->{'import_record_id'}, $biblionumber);
521                 $num_items_added += $bib_items_added;
522                 $num_items_errored += $bib_items_errored;
523             }
524             SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'match_applied');
525             SetImportRecordStatus($rowref->{'import_record_id'}, 'imported');
526         } elsif ($bib_result eq 'ignore') {
527             $num_ignored++;
528             my $biblionumber = $bib_match;
529             if (defined $biblionumber and $item_result eq 'create_new') {
530                 my ($bib_items_added, $bib_items_errored) = BatchCommitItems($rowref->{'import_record_id'}, $biblionumber);
531                 $num_items_added += $bib_items_added;
532                 $num_items_errored += $bib_items_errored;
533                 # still need to record the matched biblionumber so that the
534                 # items can be reverted
535                 my $sth2 = $dbh->prepare_cached("UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?");
536                 $sth2->execute($biblionumber, $rowref->{'import_record_id'});
537                 SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'match_applied');
538             }
539             SetImportRecordStatus($rowref->{'import_record_id'}, 'ignored');
540         }
541     }
542     $sth->finish();
543     SetImportBatchStatus($batch_id, 'imported');
544     return ($num_added, $num_updated, $num_items_added, $num_items_errored, $num_ignored);
545 }
546
547 =head2 BatchCommitItems
548
549 =over 4
550
551 ($num_items_added, $num_items_errored) = BatchCommitItems($import_record_id, $biblionumber);
552
553 =back
554
555 =cut
556
557 sub BatchCommitItems {
558     my ($import_record_id, $biblionumber) = @_;
559
560     my $dbh = C4::Context->dbh;
561
562     my $num_items_added = 0;
563     my $num_items_errored = 0;
564     my $sth = $dbh->prepare("SELECT import_items_id, import_items.marcxml, encoding
565                              FROM import_items
566                              JOIN import_records USING (import_record_id)
567                              WHERE import_record_id = ?
568                              ORDER BY import_items_id");
569     $sth->bind_param(1, $import_record_id);
570     $sth->execute();
571     while (my $row = $sth->fetchrow_hashref()) {
572         my $item_marc = MARC::Record->new_from_xml(StripNonXmlChars($row->{'marcxml'}), 'UTF-8', $row->{'encoding'});
573         # FIXME - duplicate barcode check needs to become part of AddItemFromMarc()
574         my $item = TransformMarcToKoha($dbh, $item_marc);
575         my $duplicate_barcode = exists($item->{'barcode'}) && GetItemnumberFromBarcode($item->{'barcode'});
576         if ($duplicate_barcode) {
577             my $updsth = $dbh->prepare("UPDATE import_items SET status = ?, import_error = ? WHERE import_items_id = ?");
578             $updsth->bind_param(1, 'error');
579             $updsth->bind_param(2, 'duplicate item barcode');
580             $updsth->bind_param(3, $row->{'import_items_id'});
581             $updsth->execute();
582             $num_items_errored++;
583         } else {
584             my ($item_biblionumber, $biblioitemnumber, $itemnumber) = AddItemFromMarc($item_marc, $biblionumber);
585             my $updsth = $dbh->prepare("UPDATE import_items SET status = ?, itemnumber = ? WHERE import_items_id = ?");
586             $updsth->bind_param(1, 'imported');
587             $updsth->bind_param(2, $itemnumber);
588             $updsth->bind_param(3, $row->{'import_items_id'});
589             $updsth->execute();
590             $updsth->finish();
591             $num_items_added++;
592         }
593     }
594     $sth->finish();
595     return ($num_items_added, $num_items_errored);
596 }
597
598 =head2 BatchRevertBibRecords
599
600 =over 4
601
602 my ($num_deleted, $num_errors, $num_reverted, $num_items_deleted, $num_ignored) = BatchRevertBibRecords($batch_id);
603
604 =back
605
606 =cut
607
608 sub BatchRevertBibRecords {
609     my $batch_id = shift;
610
611     my $num_deleted = 0;
612     my $num_errors = 0;
613     my $num_reverted = 0;
614     my $num_items_deleted = 0;
615     my $num_ignored = 0;
616     # commit (i.e., save, all records in the batch)
617     # FIXME biblio only at the moment
618     SetImportBatchStatus('reverting');
619     my $overlay_action = GetImportBatchOverlayAction($batch_id);
620     my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
621     my $dbh = C4::Context->dbh;
622     my $sth = $dbh->prepare("SELECT import_record_id, status, overlay_status, marcxml_old, encoding, matched_biblionumber
623                              FROM import_records
624                              JOIN import_biblios USING (import_record_id)
625                              WHERE import_batch_id = ?");
626     $sth->execute($batch_id);
627     while (my $rowref = $sth->fetchrow_hashref) {
628         if ($rowref->{'status'} eq 'error' or $rowref->{'status'} eq 'reverted') {
629             $num_ignored++;
630         }
631
632         my $bib_result = _get_revert_action($overlay_action, $rowref->{'overlay_status'}, $rowref->{'status'});
633
634         if ($bib_result eq 'delete') {
635             $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
636             my $error = DelBiblio($rowref->{'matched_biblionumber'});
637             if (defined $error) {
638                 $num_errors++;
639             } else {
640                 $num_deleted++;
641                 SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
642             }
643         } elsif ($bib_result eq 'restore') {
644             $num_reverted++;
645             my $old_record = MARC::Record->new_from_xml(StripNonXmlChars($rowref->{'marcxml_old'}), 'UTF-8', $rowref->{'encoding'});
646             my $biblionumber = $rowref->{'matched_biblionumber'};
647             my ($count, $oldbiblio) = GetBiblio($biblionumber);
648             $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
649             ModBiblio($old_record, $biblionumber, $oldbiblio->{'frameworkcode'});
650             SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
651         } elsif ($bib_result eq 'ignore') {
652             $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
653             SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
654         }
655     }
656
657     $sth->finish();
658     SetImportBatchStatus($batch_id, 'reverted');
659     return ($num_deleted, $num_errors, $num_reverted, $num_items_deleted, $num_ignored);
660 }
661
662 =head2 BatchRevertItems
663
664 =over 4
665
666 my $num_items_deleted = BatchRevertItems($import_record_id, $biblionumber);
667
668 =back
669
670 =cut
671
672 sub BatchRevertItems {
673     my ($import_record_id, $biblionumber) = @_;
674
675     my $dbh = C4::Context->dbh;
676     my $num_items_deleted = 0;
677
678     my $sth = $dbh->prepare_cached("SELECT import_items_id, itemnumber
679                                    FROM import_items
680                                    JOIN items USING (itemnumber)
681                                    WHERE import_record_id = ?");
682     $sth->bind_param(1, $import_record_id);
683     $sth->execute();
684     while (my $row = $sth->fetchrow_hashref()) {
685         DelItem($dbh, $biblionumber, $row->{'itemnumber'});
686         my $updsth = $dbh->prepare("UPDATE import_items SET status = ? WHERE import_items_id = ?");
687         $updsth->bind_param(1, 'reverted');
688         $updsth->bind_param(2, $row->{'import_items_id'});
689         $updsth->execute();
690         $updsth->finish();
691         $num_items_deleted++;
692     }
693     $sth->finish();
694     return $num_items_deleted;
695 }
696
697 =head2 GetAllImportBatches
698
699 =over 4
700
701 my $results = GetAllImportBatches();
702
703 =back
704
705 Returns a references to an array of hash references corresponding
706 to all import_batches rows (of batch_type 'batch'), sorted in 
707 ascending order by import_batch_id.
708
709 =cut
710
711 sub  GetAllImportBatches {
712     my $dbh = C4::Context->dbh;
713     my $sth = $dbh->prepare_cached("SELECT * FROM import_batches
714                                     WHERE batch_type = 'batch'
715                                     ORDER BY import_batch_id ASC");
716
717     my $results = [];
718     $sth->execute();
719     while (my $row = $sth->fetchrow_hashref) {
720         push @$results, $row;
721     }
722     $sth->finish();
723     return $results;
724 }
725
726 =head2 GetImportBatchRangeDesc
727
728 =over 4
729
730 my $results = GetImportBatchRangeDesc($offset, $results_per_group);
731
732 =back
733
734 Returns a reference to an array of hash references corresponding to
735 import_batches rows (sorted in descending order by import_batch_id)
736 start at the given offset.
737
738 =cut
739
740 sub GetImportBatchRangeDesc {
741     my ($offset, $results_per_group) = @_;
742
743     my $dbh = C4::Context->dbh;
744     my $sth = $dbh->prepare_cached("SELECT * FROM import_batches
745                                     WHERE batch_type = 'batch'
746                                     ORDER BY import_batch_id DESC
747                                     LIMIT ? OFFSET ?");
748     $sth->bind_param(1, $results_per_group);
749     $sth->bind_param(2, $offset);
750
751     my $results = [];
752     $sth->execute();
753     while (my $row = $sth->fetchrow_hashref) {
754         push @$results, $row;
755     }
756     $sth->finish();
757     return $results;
758 }
759
760 =head2 GetItemNumbersFromImportBatch
761
762 =over 4
763 =back
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                                            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 batch_id = ?");
873     $sth->execute($batch_id);
874     my ($status) = $sth->fetchrow_array();
875     $sth->finish();
876     return;
877
878 }
879
880
881 =head2 SetImportBatchStatus
882
883 =over 4
884
885 SetImportBatchStatus($batch_id, $new_status);
886
887 =back
888
889 =cut
890
891 sub SetImportBatchStatus {
892     my ($batch_id, $new_status) = @_;
893
894     my $dbh = C4::Context->dbh;
895     my $sth = $dbh->prepare("UPDATE import_batches SET import_status = ? WHERE import_batch_id = ?");
896     $sth->execute($new_status, $batch_id);
897     $sth->finish();
898
899 }
900
901 =head2 GetImportBatchOverlayAction
902
903 =over 4
904
905 my $overlay_action = GetImportBatchOverlayAction($batch_id);
906
907 =back
908
909 =cut
910
911 sub GetImportBatchOverlayAction {
912     my ($batch_id) = @_;
913
914     my $dbh = C4::Context->dbh;
915     my $sth = $dbh->prepare("SELECT overlay_action FROM import_batches WHERE import_batch_id = ?");
916     $sth->execute($batch_id);
917     my ($overlay_action) = $sth->fetchrow_array();
918     $sth->finish();
919     return $overlay_action;
920
921 }
922
923
924 =head2 SetImportBatchOverlayAction
925
926 =over 4
927
928 SetImportBatchOverlayAction($batch_id, $new_overlay_action);
929
930 =back
931
932 =cut
933
934 sub SetImportBatchOverlayAction {
935     my ($batch_id, $new_overlay_action) = @_;
936
937     my $dbh = C4::Context->dbh;
938     my $sth = $dbh->prepare("UPDATE import_batches SET overlay_action = ? WHERE import_batch_id = ?");
939     $sth->execute($new_overlay_action, $batch_id);
940     $sth->finish();
941
942 }
943
944 =head2 GetImportBatchNoMatchAction
945
946 =over 4
947
948 my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
949
950 =back
951
952 =cut
953
954 sub GetImportBatchNoMatchAction {
955     my ($batch_id) = @_;
956
957     my $dbh = C4::Context->dbh;
958     my $sth = $dbh->prepare("SELECT nomatch_action FROM import_batches WHERE import_batch_id = ?");
959     $sth->execute($batch_id);
960     my ($nomatch_action) = $sth->fetchrow_array();
961     $sth->finish();
962     return $nomatch_action;
963
964 }
965
966
967 =head2 SetImportBatchNoMatchAction
968
969 =over 4
970
971 SetImportBatchNoMatchAction($batch_id, $new_nomatch_action);
972
973 =back
974
975 =cut
976
977 sub SetImportBatchNoMatchAction {
978     my ($batch_id, $new_nomatch_action) = @_;
979
980     my $dbh = C4::Context->dbh;
981     my $sth = $dbh->prepare("UPDATE import_batches SET nomatch_action = ? WHERE import_batch_id = ?");
982     $sth->execute($new_nomatch_action, $batch_id);
983     $sth->finish();
984
985 }
986
987 =head2 GetImportBatchItemAction
988
989 =over 4
990
991 my $item_action = GetImportBatchItemAction($batch_id);
992
993 =back
994
995 =cut
996
997 sub GetImportBatchItemAction {
998     my ($batch_id) = @_;
999
1000     my $dbh = C4::Context->dbh;
1001     my $sth = $dbh->prepare("SELECT item_action FROM import_batches WHERE import_batch_id = ?");
1002     $sth->execute($batch_id);
1003     my ($item_action) = $sth->fetchrow_array();
1004     $sth->finish();
1005     return $item_action;
1006
1007 }
1008
1009
1010 =head2 SetImportBatchItemAction
1011
1012 =over 4
1013
1014 SetImportBatchItemAction($batch_id, $new_item_action);
1015
1016 =back
1017
1018 =cut
1019
1020 sub SetImportBatchItemAction {
1021     my ($batch_id, $new_item_action) = @_;
1022
1023     my $dbh = C4::Context->dbh;
1024     my $sth = $dbh->prepare("UPDATE import_batches SET item_action = ? WHERE import_batch_id = ?");
1025     $sth->execute($new_item_action, $batch_id);
1026     $sth->finish();
1027
1028 }
1029
1030 =head2 GetImportBatchMatcher
1031
1032 =over 4
1033
1034 my $matcher_id = GetImportBatchMatcher($batch_id);
1035
1036 =back
1037
1038 =cut
1039
1040 sub GetImportBatchMatcher {
1041     my ($batch_id) = @_;
1042
1043     my $dbh = C4::Context->dbh;
1044     my $sth = $dbh->prepare("SELECT matcher_id FROM import_batches WHERE import_batch_id = ?");
1045     $sth->execute($batch_id);
1046     my ($matcher_id) = $sth->fetchrow_array();
1047     $sth->finish();
1048     return $matcher_id;
1049
1050 }
1051
1052
1053 =head2 SetImportBatchMatcher
1054
1055 =over 4
1056
1057 SetImportBatchMatcher($batch_id, $new_matcher_id);
1058
1059 =back
1060
1061 =cut
1062
1063 sub SetImportBatchMatcher {
1064     my ($batch_id, $new_matcher_id) = @_;
1065
1066     my $dbh = C4::Context->dbh;
1067     my $sth = $dbh->prepare("UPDATE import_batches SET matcher_id = ? WHERE import_batch_id = ?");
1068     $sth->execute($new_matcher_id, $batch_id);
1069     $sth->finish();
1070
1071 }
1072
1073 =head2 GetImportRecordOverlayStatus
1074
1075 =over 4
1076
1077 my $overlay_status = GetImportRecordOverlayStatus($import_record_id);
1078
1079 =back
1080
1081 =cut
1082
1083 sub GetImportRecordOverlayStatus {
1084     my ($import_record_id) = @_;
1085
1086     my $dbh = C4::Context->dbh;
1087     my $sth = $dbh->prepare("SELECT overlay_status FROM import_records WHERE import_record_id = ?");
1088     $sth->execute($import_record_id);
1089     my ($overlay_status) = $sth->fetchrow_array();
1090     $sth->finish();
1091     return $overlay_status;
1092
1093 }
1094
1095
1096 =head2 SetImportRecordOverlayStatus
1097
1098 =over 4
1099
1100 SetImportRecordOverlayStatus($import_record_id, $new_overlay_status);
1101
1102 =back
1103
1104 =cut
1105
1106 sub SetImportRecordOverlayStatus {
1107     my ($import_record_id, $new_overlay_status) = @_;
1108
1109     my $dbh = C4::Context->dbh;
1110     my $sth = $dbh->prepare("UPDATE import_records SET overlay_status = ? WHERE import_record_id = ?");
1111     $sth->execute($new_overlay_status, $import_record_id);
1112     $sth->finish();
1113
1114 }
1115
1116 =head2 GetImportRecordStatus
1117
1118 =over 4
1119
1120 my $overlay_status = GetImportRecordStatus($import_record_id);
1121
1122 =back
1123
1124 =cut
1125
1126 sub GetImportRecordStatus {
1127     my ($import_record_id) = @_;
1128
1129     my $dbh = C4::Context->dbh;
1130     my $sth = $dbh->prepare("SELECT status FROM import_records WHERE import_record_id = ?");
1131     $sth->execute($import_record_id);
1132     my ($overlay_status) = $sth->fetchrow_array();
1133     $sth->finish();
1134     return $overlay_status;
1135
1136 }
1137
1138
1139 =head2 SetImportRecordStatus
1140
1141 =over 4
1142
1143 SetImportRecordStatus($import_record_id, $new_overlay_status);
1144
1145 =back
1146
1147 =cut
1148
1149 sub SetImportRecordStatus {
1150     my ($import_record_id, $new_overlay_status) = @_;
1151
1152     my $dbh = C4::Context->dbh;
1153     my $sth = $dbh->prepare("UPDATE import_records SET status = ? WHERE import_record_id = ?");
1154     $sth->execute($new_overlay_status, $import_record_id);
1155     $sth->finish();
1156
1157 }
1158
1159 =head2 GetImportRecordMatches
1160
1161 =over 4
1162
1163 my $results = GetImportRecordMatches($import_record_id, $best_only);
1164
1165 =back
1166
1167 =cut
1168
1169 sub GetImportRecordMatches {
1170     my $import_record_id = shift;
1171     my $best_only = @_ ? shift : 0;
1172
1173     my $dbh = C4::Context->dbh;
1174     # FIXME currently biblio only
1175     my $sth = $dbh->prepare_cached("SELECT title, author, biblionumber, score
1176                                     FROM import_records
1177                                     JOIN import_record_matches USING (import_record_id)
1178                                     JOIN biblio ON (biblionumber = candidate_match_id)
1179                                     WHERE import_record_id = ?
1180                                     ORDER BY score DESC, biblionumber DESC");
1181     $sth->bind_param(1, $import_record_id);
1182     my $results = [];
1183     $sth->execute();
1184     while (my $row = $sth->fetchrow_hashref) {
1185         push @$results, $row;
1186         last if $best_only;
1187     }
1188     $sth->finish();
1189
1190     return $results;
1191     
1192 }
1193
1194
1195 =head2 SetImportRecordMatches
1196
1197 =over 4
1198
1199 SetImportRecordMatches($import_record_id, @matches);
1200
1201 =back
1202
1203 =cut
1204
1205 sub SetImportRecordMatches {
1206     my $import_record_id = shift;
1207     my @matches = @_;
1208
1209     my $dbh = C4::Context->dbh;
1210     my $delsth = $dbh->prepare("DELETE FROM import_record_matches WHERE import_record_id = ?");
1211     $delsth->execute($import_record_id);
1212     $delsth->finish();
1213
1214     my $sth = $dbh->prepare("INSERT INTO import_record_matches (import_record_id, candidate_match_id, score)
1215                                     VALUES (?, ?, ?)");
1216     foreach my $match (@matches) {
1217         $sth->execute($import_record_id, $match->{'record_id'}, $match->{'score'});
1218     }
1219 }
1220
1221
1222 # internal functions
1223
1224 sub _create_import_record {
1225     my ($batch_id, $record_sequence, $marc_record, $record_type, $encoding, $z3950random) = @_;
1226
1227     my $dbh = C4::Context->dbh;
1228     my $sth = $dbh->prepare("INSERT INTO import_records (import_batch_id, record_sequence, marc, marcxml, 
1229                                                          record_type, encoding, z3950random)
1230                                     VALUES (?, ?, ?, ?, ?, ?, ?)");
1231     $sth->execute($batch_id, $record_sequence, $marc_record->as_usmarc(), $marc_record->as_xml(),
1232                   $record_type, $encoding, $z3950random);
1233     my $import_record_id = $dbh->{'mysql_insertid'};
1234     $sth->finish();
1235     return $import_record_id;
1236 }
1237
1238 sub _update_import_record_marc {
1239     my ($import_record_id, $marc_record) = @_;
1240
1241     my $dbh = C4::Context->dbh;
1242     my $sth = $dbh->prepare("UPDATE import_records SET marc = ?, marcxml = ?
1243                              WHERE  import_record_id = ?");
1244     $sth->execute($marc_record->as_usmarc(), $marc_record->as_xml(), $import_record_id);
1245     $sth->finish();
1246 }
1247
1248 sub _add_biblio_fields {
1249     my ($import_record_id, $marc_record) = @_;
1250
1251     my ($title, $author, $isbn, $issn) = _parse_biblio_fields($marc_record);
1252     my $dbh = C4::Context->dbh;
1253     # FIXME no controlnumber, originalsource
1254     # FIXME 2 - should regularize normalization of ISBN wherever it is done
1255     $isbn =~ s/\(.*$//;
1256     $isbn =~ tr/ -_//;  
1257     $isbn = uc $isbn;
1258     my $sth = $dbh->prepare("INSERT INTO import_biblios (import_record_id, title, author, isbn, issn) VALUES (?, ?, ?, ?, ?)");
1259     $sth->execute($import_record_id, $title, $author, $isbn, $issn);
1260     $sth->finish();
1261                 
1262 }
1263
1264 sub _update_biblio_fields {
1265     my ($import_record_id, $marc_record) = @_;
1266
1267     my ($title, $author, $isbn, $issn) = _parse_biblio_fields($marc_record);
1268     my $dbh = C4::Context->dbh;
1269     # FIXME no controlnumber, originalsource
1270     # FIXME 2 - should regularize normalization of ISBN wherever it is done
1271     $isbn =~ s/\(.*$//;
1272     $isbn =~ tr/ -_//;
1273     $isbn = uc $isbn;
1274     my $sth = $dbh->prepare("UPDATE import_biblios SET title = ?, author = ?, isbn = ?, issn = ?
1275                              WHERE  import_record_id = ?");
1276     $sth->execute($title, $author, $isbn, $issn, $import_record_id);
1277     $sth->finish();
1278 }
1279
1280 sub _parse_biblio_fields {
1281     my ($marc_record) = @_;
1282
1283     my $dbh = C4::Context->dbh;
1284     my $bibliofields = TransformMarcToKoha($dbh, $marc_record, '');
1285     return ($bibliofields->{'title'}, $bibliofields->{'author'}, $bibliofields->{'isbn'}, $bibliofields->{'issn'});
1286
1287 }
1288
1289 sub _update_batch_record_counts {
1290     my ($batch_id) = @_;
1291
1292     my $dbh = C4::Context->dbh;
1293     my $sth = $dbh->prepare_cached("UPDATE import_batches SET num_biblios = (
1294                                     SELECT COUNT(*)
1295                                     FROM import_records
1296                                     WHERE import_batch_id = import_batches.import_batch_id
1297                                     AND record_type = 'biblio')
1298                                     WHERE import_batch_id = ?");
1299     $sth->bind_param(1, $batch_id);
1300     $sth->execute();
1301     $sth->finish();
1302     $sth = $dbh->prepare_cached("UPDATE import_batches SET num_items = (
1303                                     SELECT COUNT(*)
1304                                     FROM import_records
1305                                     JOIN import_items USING (import_record_id)
1306                                     WHERE import_batch_id = import_batches.import_batch_id
1307                                     AND record_type = 'biblio')
1308                                     WHERE import_batch_id = ?");
1309     $sth->bind_param(1, $batch_id);
1310     $sth->execute();
1311     $sth->finish();
1312
1313 }
1314
1315 sub _get_commit_action {
1316     my ($overlay_action, $nomatch_action, $item_action, $overlay_status, $import_record_id) = @_;
1317     
1318     my ($bib_result, $bib_match, $item_result);
1319
1320     if ($overlay_status ne 'no_match') {
1321         $bib_match = GetBestRecordMatch($import_record_id);
1322         if ($overlay_action eq 'replace') {
1323             $bib_result  = defined($bib_match) ? 'replace' : 'create_new';
1324         } elsif ($overlay_action eq 'create_new') {
1325             $bib_result  = 'create_new';
1326         } elsif ($overlay_action eq 'ignore') {
1327             $bib_result  = 'ignore';
1328         } 
1329         $item_result = ($item_action eq 'always_add' or $item_action eq 'add_only_for_matches') ? 'create_new' : 'ignore';
1330     } else {
1331         $bib_result = $nomatch_action;
1332         $item_result = ($item_action eq 'always_add' or $item_action eq 'add_only_for_new')     ? 'create_new' : 'ignore';
1333     }
1334
1335     return ($bib_result, $item_result, $bib_match);
1336 }
1337
1338 sub _get_revert_action {
1339     my ($overlay_action, $overlay_status, $status) = @_;
1340
1341     my $bib_result;
1342
1343     if ($status eq 'ignored') {
1344         $bib_result = 'ignore';
1345     } else {
1346         if ($overlay_action eq 'create_new') {
1347             $bib_result = 'delete';
1348         } else {
1349             $bib_result = ($overlay_status eq 'match_applied') ? 'restore' : 'delete';
1350         }
1351     }
1352     return $bib_result;
1353 }
1354
1355 1;
1356 __END__
1357
1358 =head1 AUTHOR
1359
1360 Koha Development Team <info@koha.org>
1361
1362 Galen Charlton <galen.charlton@liblime.com>
1363
1364 =cut