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