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