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