Bug 10558 [QA Follow-up]
[koha.git] / C4 / ImportBatch.pm
1 package C4::ImportBatch;
2
3 # Copyright (C) 2007 LibLime, 2012 C & P Bibliography Services
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 use C4::AuthoritiesMarc;
29 use C4::MarcModificationTemplates;
30
31 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
32
33 BEGIN {
34         # set the version for version checking
35     $VERSION = 3.07.00.049;
36         require Exporter;
37         @ISA    = qw(Exporter);
38         @EXPORT = qw(
39     GetZ3950BatchId
40     GetWebserviceBatchId
41     GetImportRecordMarc
42     GetImportRecordMarcXML
43     AddImportBatch
44     GetImportBatch
45     AddAuthToBatch
46     AddBiblioToBatch
47     AddItemsToImportBiblio
48     ModAuthorityInBatch
49     ModBiblioInBatch
50
51     BatchStageMarcRecords
52     BatchFindDuplicates
53     BatchCommitRecords
54     BatchRevertRecords
55     CleanBatch
56
57     GetAllImportBatches
58     GetStagedWebserviceBatches
59     GetImportBatchRangeDesc
60     GetNumberOfNonZ3950ImportBatches
61     GetImportRecordsRange
62         GetItemNumbersFromImportBatch
63     
64     GetImportBatchStatus
65     SetImportBatchStatus
66     GetImportBatchOverlayAction
67     SetImportBatchOverlayAction
68     GetImportBatchNoMatchAction
69     SetImportBatchNoMatchAction
70     GetImportBatchItemAction
71     SetImportBatchItemAction
72     GetImportBatchMatcher
73     SetImportBatchMatcher
74     GetImportRecordOverlayStatus
75     SetImportRecordOverlayStatus
76     GetImportRecordStatus
77     SetImportRecordStatus
78     GetImportRecordMatches
79     SetImportRecordMatches
80         );
81 }
82
83 =head1 NAME
84
85 C4::ImportBatch - manage batches of imported MARC records
86
87 =head1 SYNOPSIS
88
89 use C4::ImportBatch;
90
91 =head1 FUNCTIONS
92
93 =head2 GetZ3950BatchId
94
95   my $batchid = GetZ3950BatchId($z3950server);
96
97 Retrieves the ID of the import batch for the Z39.50
98 reservoir for the given target.  If necessary,
99 creates the import batch.
100
101 =cut
102
103 sub GetZ3950BatchId {
104     my ($z3950server) = @_;
105
106     my $dbh = C4::Context->dbh;
107     my $sth = $dbh->prepare("SELECT import_batch_id FROM import_batches
108                              WHERE  batch_type = 'z3950'
109                              AND    file_name = ?");
110     $sth->execute($z3950server);
111     my $rowref = $sth->fetchrow_arrayref();
112     $sth->finish();
113     if (defined $rowref) {
114         return $rowref->[0];
115     } else {
116         my $batch_id = AddImportBatch( {
117                 overlay_action => 'create_new',
118                 import_status => 'staged',
119                 batch_type => 'z3950',
120                 file_name => $z3950server,
121             } );
122         return $batch_id;
123     }
124     
125 }
126
127 =head2 GetWebserviceBatchId
128
129   my $batchid = GetWebserviceBatchId();
130
131 Retrieves the ID of the import batch for webservice.
132 If necessary, creates the import batch.
133
134 =cut
135
136 my $WEBSERVICE_BASE_QRY = <<EOQ;
137 SELECT import_batch_id FROM import_batches
138 WHERE  batch_type = 'webservice'
139 AND    import_status = 'staged'
140 EOQ
141 sub GetWebserviceBatchId {
142     my ($params) = @_;
143
144     my $dbh = C4::Context->dbh;
145     my $sql = $WEBSERVICE_BASE_QRY;
146     my @args;
147     foreach my $field (qw(matcher_id overlay_action nomatch_action item_action)) {
148         if (my $val = $params->{$field}) {
149             $sql .= " AND $field = ?";
150             push @args, $val;
151         }
152     }
153     my $id = $dbh->selectrow_array($sql, undef, @args);
154     return $id if $id;
155
156     $params->{batch_type} = 'webservice';
157     $params->{import_status} = 'staged';
158     return AddImportBatch($params);
159 }
160
161 =head2 GetImportRecordMarc
162
163   my ($marcblob, $encoding) = GetImportRecordMarc($import_record_id);
164
165 =cut
166
167 sub GetImportRecordMarc {
168     my ($import_record_id) = @_;
169
170     my $dbh = C4::Context->dbh;
171     my $sth = $dbh->prepare("SELECT marc, encoding FROM import_records WHERE import_record_id = ?");
172     $sth->execute($import_record_id);
173     my ($marc, $encoding) = $sth->fetchrow();
174     $sth->finish();
175     return $marc, $encoding;
176
177 }
178
179 =head2 GetImportRecordMarcXML
180
181   my $marcxml = GetImportRecordMarcXML($import_record_id);
182
183 =cut
184
185 sub GetImportRecordMarcXML {
186     my ($import_record_id) = @_;
187
188     my $dbh = C4::Context->dbh;
189     my $sth = $dbh->prepare("SELECT marcxml FROM import_records WHERE import_record_id = ?");
190     $sth->execute($import_record_id);
191     my ($marcxml) = $sth->fetchrow();
192     $sth->finish();
193     return $marcxml;
194
195 }
196
197 =head2 AddImportBatch
198
199   my $batch_id = AddImportBatch($params_hash);
200
201 =cut
202
203 sub AddImportBatch {
204     my ($params) = @_;
205
206     my (@fields, @vals);
207     foreach (qw( matcher_id template_id branchcode
208                  overlay_action nomatch_action item_action
209                  import_status batch_type file_name comments record_type )) {
210         if (exists $params->{$_}) {
211             push @fields, $_;
212             push @vals, $params->{$_};
213         }
214     }
215     my $dbh = C4::Context->dbh;
216     $dbh->do("INSERT INTO import_batches (".join( ',', @fields).")
217                                   VALUES (".join( ',', map '?', @fields).")",
218              undef,
219              @vals);
220     return $dbh->{'mysql_insertid'};
221 }
222
223 =head2 GetImportBatch 
224
225   my $row = GetImportBatch($batch_id);
226
227 Retrieve a hashref of an import_batches row.
228
229 =cut
230
231 sub GetImportBatch {
232     my ($batch_id) = @_;
233
234     my $dbh = C4::Context->dbh;
235     my $sth = $dbh->prepare_cached("SELECT * FROM import_batches WHERE import_batch_id = ?");
236     $sth->bind_param(1, $batch_id);
237     $sth->execute();
238     my $result = $sth->fetchrow_hashref;
239     $sth->finish();
240     return $result;
241
242 }
243
244 =head2 AddBiblioToBatch 
245
246   my $import_record_id = AddBiblioToBatch($batch_id, $record_sequence, 
247                 $marc_record, $encoding, $z3950random, $update_counts);
248
249 =cut
250
251 sub AddBiblioToBatch {
252     my $batch_id = shift;
253     my $record_sequence = shift;
254     my $marc_record = shift;
255     my $encoding = shift;
256     my $z3950random = shift;
257     my $update_counts = @_ ? shift : 1;
258
259     my $import_record_id = _create_import_record($batch_id, $record_sequence, $marc_record, 'biblio', $encoding, $z3950random, C4::Context->preference('marcflavour'));
260     _add_biblio_fields($import_record_id, $marc_record);
261     _update_batch_record_counts($batch_id) if $update_counts;
262     return $import_record_id;
263 }
264
265 =head2 ModBiblioInBatch
266
267   ModBiblioInBatch($import_record_id, $marc_record);
268
269 =cut
270
271 sub ModBiblioInBatch {
272     my ($import_record_id, $marc_record) = @_;
273
274     _update_import_record_marc($import_record_id, $marc_record, C4::Context->preference('marcflavour'));
275     _update_biblio_fields($import_record_id, $marc_record);
276
277 }
278
279 =head2 AddAuthToBatch
280
281   my $import_record_id = AddAuthToBatch($batch_id, $record_sequence,
282                 $marc_record, $encoding, $z3950random, $update_counts, [$marc_type]);
283
284 =cut
285
286 sub AddAuthToBatch {
287     my $batch_id = shift;
288     my $record_sequence = shift;
289     my $marc_record = shift;
290     my $encoding = shift;
291     my $z3950random = shift;
292     my $update_counts = @_ ? shift : 1;
293     my $marc_type = shift || C4::Context->preference('marcflavour');
294
295     $marc_type = 'UNIMARCAUTH' if $marc_type eq 'UNIMARC';
296
297     my $import_record_id = _create_import_record($batch_id, $record_sequence, $marc_record, 'auth', $encoding, $z3950random, $marc_type);
298     _add_auth_fields($import_record_id, $marc_record);
299     _update_batch_record_counts($batch_id) if $update_counts;
300     return $import_record_id;
301 }
302
303 =head2 ModAuthInBatch
304
305   ModAuthInBatch($import_record_id, $marc_record);
306
307 =cut
308
309 sub ModAuthInBatch {
310     my ($import_record_id, $marc_record) = @_;
311
312     my $marcflavour = C4::Context->preference('marcflavour');
313     _update_import_record_marc($import_record_id, $marc_record, $marcflavour eq 'UNIMARC' ? 'UNIMARCAUTH' : 'USMARC');
314
315 }
316
317 =head2 BatchStageMarcRecords
318
319   ($batch_id, $num_records, $num_items, @invalid_records) = 
320     BatchStageMarcRecords($encoding, $marc_records, $file_name, $marc_modification_template,
321                           $comments, $branch_code, $parse_items,
322                           $leave_as_staging, 
323                           $progress_interval, $progress_callback);
324
325 =cut
326
327 sub BatchStageMarcRecords {
328     my $record_type = shift;
329     my $encoding = shift;
330     my $marc_records = shift;
331     my $file_name = shift;
332     my $marc_modification_template = shift;
333     my $comments = shift;
334     my $branch_code = shift;
335     my $parse_items = shift;
336     my $leave_as_staging = shift;
337
338     # optional callback to monitor status 
339     # of job
340     my $progress_interval = 0;
341     my $progress_callback = undef;
342     if ($#_ == 1) {
343         $progress_interval = shift;
344         $progress_callback = shift;
345         $progress_interval = 0 unless $progress_interval =~ /^\d+$/ and $progress_interval > 0;
346         $progress_interval = 0 unless 'CODE' eq ref $progress_callback;
347     } 
348     
349     my $batch_id = AddImportBatch( {
350             overlay_action => 'create_new',
351             import_status => 'staging',
352             batch_type => 'batch',
353             file_name => $file_name,
354             comments => $comments,
355             record_type => $record_type,
356         } );
357     if ($parse_items) {
358         SetImportBatchItemAction($batch_id, 'always_add');
359     } else {
360         SetImportBatchItemAction($batch_id, 'ignore');
361     }
362
363     my $marc_type = C4::Context->preference('marcflavour');
364     $marc_type .= 'AUTH' if ($marc_type eq 'UNIMARC' && $record_type eq 'auth');
365     my @invalid_records = ();
366     my $num_valid = 0;
367     my $num_items = 0;
368     # FIXME - for now, we're dealing only with bibs
369     my $rec_num = 0;
370     foreach my $marc_blob (split(/\x1D/, $marc_records)) {
371         $marc_blob =~ s/^\s+//g;
372         $marc_blob =~ s/\s+$//g;
373         next unless $marc_blob;
374         $rec_num++;
375         if ($progress_interval and (0 == ($rec_num % $progress_interval))) {
376             &$progress_callback($rec_num);
377         }
378         my ($marc_record, $charset_guessed, $char_errors) =
379             MarcToUTF8Record($marc_blob, $marc_type, $encoding);
380
381         $encoding = $charset_guessed unless $encoding;
382
383         ModifyRecordWithTemplate( $marc_modification_template, $marc_record ) if ( $marc_modification_template );
384
385         my $import_record_id;
386         if (scalar($marc_record->fields()) == 0) {
387             push @invalid_records, $marc_blob;
388         } else {
389
390             # Normalize the record so it doesn't have separated diacritics
391             SetUTF8Flag($marc_record);
392
393             $num_valid++;
394             if ($record_type eq 'biblio') {
395                 $import_record_id = AddBiblioToBatch($batch_id, $rec_num, $marc_record, $encoding, int(rand(99999)), 0);
396                 if ($parse_items) {
397                     my @import_items_ids = AddItemsToImportBiblio($batch_id, $import_record_id, $marc_record, 0);
398                     $num_items += scalar(@import_items_ids);
399                 }
400             } elsif ($record_type eq 'auth') {
401                 $import_record_id = AddAuthToBatch($batch_id, $rec_num, $marc_record, $encoding, int(rand(99999)), 0, $marc_type);
402             }
403         }
404     }
405     unless ($leave_as_staging) {
406         SetImportBatchStatus($batch_id, 'staged');
407     }
408     # FIXME branch_code, number of bibs, number of items
409     _update_batch_record_counts($batch_id);
410     return ($batch_id, $num_valid, $num_items, @invalid_records);
411 }
412
413 =head2 AddItemsToImportBiblio
414
415   my @import_items_ids = AddItemsToImportBiblio($batch_id, 
416                 $import_record_id, $marc_record, $update_counts);
417
418 =cut
419
420 sub AddItemsToImportBiblio {
421     my $batch_id = shift;
422     my $import_record_id = shift;
423     my $marc_record = shift;
424     my $update_counts = @_ ? shift : 0;
425
426     my @import_items_ids = ();
427    
428     my $dbh = C4::Context->dbh; 
429     my ($item_tag,$item_subfield) = &GetMarcFromKohaField("items.itemnumber",'');
430     foreach my $item_field ($marc_record->field($item_tag)) {
431         my $item_marc = MARC::Record->new();
432         $item_marc->leader("00000    a              "); # must set Leader/09 to 'a'
433         $item_marc->append_fields($item_field);
434         $marc_record->delete_field($item_field);
435         my $sth = $dbh->prepare_cached("INSERT INTO import_items (import_record_id, status, marcxml)
436                                         VALUES (?, ?, ?)");
437         $sth->bind_param(1, $import_record_id);
438         $sth->bind_param(2, 'staged');
439         $sth->bind_param(3, $item_marc->as_xml());
440         $sth->execute();
441         push @import_items_ids, $dbh->{'mysql_insertid'};
442         $sth->finish();
443     }
444
445     if ($#import_items_ids > -1) {
446         _update_batch_record_counts($batch_id) if $update_counts;
447         _update_import_record_marc($import_record_id, $marc_record, C4::Context->preference('marcflavour'));
448     }
449     return @import_items_ids;
450 }
451
452 =head2 BatchFindDuplicates
453
454   my $num_with_matches = BatchFindDuplicates($batch_id, $matcher,
455              $max_matches, $progress_interval, $progress_callback);
456
457 Goes through the records loaded in the batch and attempts to 
458 find duplicates for each one.  Sets the matching status 
459 of each record to "no_match" or "auto_match" as appropriate.
460
461 The $max_matches parameter is optional; if it is not supplied,
462 it defaults to 10.
463
464 The $progress_interval and $progress_callback parameters are 
465 optional; if both are supplied, the sub referred to by
466 $progress_callback will be invoked every $progress_interval
467 records using the number of records processed as the 
468 singular argument.
469
470 =cut
471
472 sub BatchFindDuplicates {
473     my $batch_id = shift;
474     my $matcher = shift;
475     my $max_matches = @_ ? shift : 10;
476
477     # optional callback to monitor status 
478     # of job
479     my $progress_interval = 0;
480     my $progress_callback = undef;
481     if ($#_ == 1) {
482         $progress_interval = shift;
483         $progress_callback = shift;
484         $progress_interval = 0 unless $progress_interval =~ /^\d+$/ and $progress_interval > 0;
485         $progress_interval = 0 unless 'CODE' eq ref $progress_callback;
486     }
487
488     my $dbh = C4::Context->dbh;
489
490     my $sth = $dbh->prepare("SELECT import_record_id, record_type, marc
491                              FROM import_records
492                              WHERE import_batch_id = ?");
493     $sth->execute($batch_id);
494     my $num_with_matches = 0;
495     my $rec_num = 0;
496     while (my $rowref = $sth->fetchrow_hashref) {
497         $rec_num++;
498         if ($progress_interval and (0 == ($rec_num % $progress_interval))) {
499             &$progress_callback($rec_num);
500         }
501         my $marc_record = MARC::Record->new_from_usmarc($rowref->{'marc'});
502         my @matches = ();
503         if (defined $matcher) {
504             @matches = $matcher->get_matches($marc_record, $max_matches);
505         }
506         if (scalar(@matches) > 0) {
507             $num_with_matches++;
508             SetImportRecordMatches($rowref->{'import_record_id'}, @matches);
509             SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'auto_match');
510         } else {
511             SetImportRecordMatches($rowref->{'import_record_id'}, ());
512             SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'no_match');
513         }
514     }
515     $sth->finish();
516     return $num_with_matches;
517 }
518
519 =head2 BatchCommitRecords
520
521   my ($num_added, $num_updated, $num_items_added, $num_items_replaced, $num_items_errored, $num_ignored) =
522         BatchCommitRecords($batch_id, $framework,
523         $progress_interval, $progress_callback);
524
525 =cut
526
527 sub BatchCommitRecords {
528     my $batch_id = shift;
529     my $framework = shift;
530
531     # optional callback to monitor status 
532     # of job
533     my $progress_interval = 0;
534     my $progress_callback = undef;
535     if ($#_ == 1) {
536         $progress_interval = shift;
537         $progress_callback = shift;
538         $progress_interval = 0 unless $progress_interval =~ /^\d+$/ and $progress_interval > 0;
539         $progress_interval = 0 unless 'CODE' eq ref $progress_callback;
540     }
541
542     my $record_type;
543     my $num_added = 0;
544     my $num_updated = 0;
545     my $num_items_added = 0;
546     my $num_items_replaced = 0;
547     my $num_items_errored = 0;
548     my $num_ignored = 0;
549     # commit (i.e., save, all records in the batch)
550     SetImportBatchStatus('importing');
551     my $overlay_action = GetImportBatchOverlayAction($batch_id);
552     my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
553     my $item_action = GetImportBatchItemAction($batch_id);
554     my $item_tag;
555     my $item_subfield;
556     my $dbh = C4::Context->dbh;
557     my $sth = $dbh->prepare("SELECT import_records.import_record_id, record_type, status, overlay_status, marc, encoding
558                              FROM import_records
559                              LEFT JOIN import_auths ON (import_records.import_record_id=import_auths.import_record_id)
560                              LEFT JOIN import_biblios ON (import_records.import_record_id=import_biblios.import_record_id)
561                              WHERE import_batch_id = ?");
562     $sth->execute($batch_id);
563     my $marcflavour = C4::Context->preference('marcflavour');
564     my $rec_num = 0;
565     while (my $rowref = $sth->fetchrow_hashref) {
566         $record_type = $rowref->{'record_type'};
567         $rec_num++;
568         if ($progress_interval and (0 == ($rec_num % $progress_interval))) {
569             &$progress_callback($rec_num);
570         }
571         if ($rowref->{'status'} eq 'error' or $rowref->{'status'} eq 'imported') {
572             $num_ignored++;
573             next;
574         }
575
576         my $marc_type;
577         if ($marcflavour eq 'UNIMARC' && $record_type eq 'auth') {
578             $marc_type = 'UNIMARCAUTH';
579         } elsif ($marcflavour eq 'UNIMARC') {
580             $marc_type = 'UNIMARC';
581         } else {
582             $marc_type = 'USMARC';
583         }
584         my $marc_record = MARC::Record->new_from_usmarc($rowref->{'marc'});
585
586         if ($record_type eq 'biblio') {
587             # remove any item tags - rely on BatchCommitItems
588             ($item_tag,$item_subfield) = &GetMarcFromKohaField("items.itemnumber",'');
589             foreach my $item_field ($marc_record->field($item_tag)) {
590                 $marc_record->delete_field($item_field);
591             }
592         }
593
594         my ($record_result, $item_result, $record_match) =
595             _get_commit_action($overlay_action, $nomatch_action, $item_action, 
596                                $rowref->{'overlay_status'}, $rowref->{'import_record_id'}, $record_type);
597
598         my $recordid;
599         my $query;
600         if ($record_result eq 'create_new') {
601             $num_added++;
602             if ($record_type eq 'biblio') {
603                 my $biblioitemnumber;
604                 ($recordid, $biblioitemnumber) = AddBiblio($marc_record, $framework);
605                 $query = "UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?";
606                 if ($item_result eq 'create_new' || $item_result eq 'replace') {
607                     my ($bib_items_added, $bib_items_replaced, $bib_items_errored) = BatchCommitItems($rowref->{'import_record_id'}, $recordid, $item_result);
608                     $num_items_added += $bib_items_added;
609                     $num_items_replaced += $bib_items_replaced;
610                     $num_items_errored += $bib_items_errored;
611                 }
612             } else {
613                 $recordid = AddAuthority($marc_record, undef, GuessAuthTypeCode($marc_record));
614                 $query = "UPDATE import_auths SET matched_authid = ? WHERE import_record_id = ?";
615             }
616             my $sth = $dbh->prepare_cached($query);
617             $sth->execute($recordid, $rowref->{'import_record_id'});
618             $sth->finish();
619             SetImportRecordStatus($rowref->{'import_record_id'}, 'imported');
620         } elsif ($record_result eq 'replace') {
621             $num_updated++;
622             $recordid = $record_match;
623             my $oldxml;
624             if ($record_type eq 'biblio') {
625                 my ($count, $oldbiblio) = GetBiblio($recordid);
626                 $oldxml = GetXmlBiblio($recordid);
627
628                 # remove item fields so that they don't get
629                 # added again if record is reverted
630                 # FIXME: GetXmlBiblio output should not contain item info any more! So the next foreach should not be needed. Does not hurt either; may remove old 952s that should not have been there anymore.
631                 my $old_marc = MARC::Record->new_from_xml(StripNonXmlChars($oldxml), 'UTF-8', $rowref->{'encoding'}, $marc_type);
632                 foreach my $item_field ($old_marc->field($item_tag)) {
633                     $old_marc->delete_field($item_field);
634                 }
635                 $oldxml = $old_marc->as_xml($marc_type);
636
637                 ModBiblio($marc_record, $recordid, $oldbiblio->{'frameworkcode'});
638                 $query = "UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?";
639
640                 if ($item_result eq 'create_new' || $item_result eq 'replace') {
641                     my ($bib_items_added, $bib_items_replaced, $bib_items_errored) = BatchCommitItems($rowref->{'import_record_id'}, $recordid, $item_result);
642                     $num_items_added += $bib_items_added;
643                     $num_items_replaced += $bib_items_replaced;
644                     $num_items_errored += $bib_items_errored;
645                 }
646             } else {
647                 $oldxml = GetAuthorityXML($recordid);
648
649                 ModAuthority($recordid, $marc_record, GuessAuthTypeCode($marc_record));
650                 $query = "UPDATE import_auths SET matched_authid = ? WHERE import_record_id = ?";
651             }
652             my $sth = $dbh->prepare_cached("UPDATE import_records SET marcxml_old = ? WHERE import_record_id = ?");
653             $sth->execute($oldxml, $rowref->{'import_record_id'});
654             $sth->finish();
655             my $sth2 = $dbh->prepare_cached($query);
656             $sth2->execute($recordid, $rowref->{'import_record_id'});
657             $sth2->finish();
658             SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'match_applied');
659             SetImportRecordStatus($rowref->{'import_record_id'}, 'imported');
660         } elsif ($record_result eq 'ignore') {
661             $recordid = $record_match;
662             $num_ignored++;
663             $recordid = $record_match;
664             if ($record_type eq 'biblio' and defined $recordid and ( $item_result eq 'create_new' || $item_result eq 'replace' ) ) {
665                 my ($bib_items_added, $bib_items_replaced, $bib_items_errored) = BatchCommitItems($rowref->{'import_record_id'}, $recordid, $item_result);
666                 $num_items_added += $bib_items_added;
667          $num_items_replaced += $bib_items_replaced;
668                 $num_items_errored += $bib_items_errored;
669                 # still need to record the matched biblionumber so that the
670                 # items can be reverted
671                 my $sth2 = $dbh->prepare_cached("UPDATE import_biblios SET matched_biblionumber = ? WHERE import_record_id = ?");
672                 $sth2->execute($recordid, $rowref->{'import_record_id'});
673                 SetImportRecordOverlayStatus($rowref->{'import_record_id'}, 'match_applied');
674             }
675             SetImportRecordStatus($rowref->{'import_record_id'}, 'ignored');
676         }
677     }
678     $sth->finish();
679     SetImportBatchStatus($batch_id, 'imported');
680     return ($num_added, $num_updated, $num_items_added, $num_items_replaced, $num_items_errored, $num_ignored);
681 }
682
683 =head2 BatchCommitItems
684
685   ($num_items_added, $num_items_errored) = 
686          BatchCommitItems($import_record_id, $biblionumber);
687
688 =cut
689
690 sub BatchCommitItems {
691     my ( $import_record_id, $biblionumber, $action ) = @_;
692
693     my $dbh = C4::Context->dbh;
694
695     my $num_items_added = 0;
696     my $num_items_errored = 0;
697     my $num_items_replaced = 0;
698
699     my $sth = $dbh->prepare( "
700         SELECT import_items_id, import_items.marcxml, encoding
701         FROM import_items
702         JOIN import_records USING (import_record_id)
703         WHERE import_record_id = ?
704         ORDER BY import_items_id
705     " );
706     $sth->bind_param( 1, $import_record_id );
707     $sth->execute();
708
709     while ( my $row = $sth->fetchrow_hashref() ) {
710         my $item_marc = MARC::Record->new_from_xml( StripNonXmlChars( $row->{'marcxml'} ), 'UTF-8', $row->{'encoding'} );
711
712         # Delete date_due subfield as to not accidentally delete item checkout due dates
713         my ( $MARCfield, $MARCsubfield ) = GetMarcFromKohaField( 'items.onloan', GetFrameworkCode($biblionumber) );
714         $item_marc->field($MARCfield)->delete_subfield( code => $MARCsubfield );
715
716         my $item = TransformMarcToKoha( $dbh, $item_marc );
717
718         my $duplicate_barcode = exists( $item->{'barcode'} ) && GetItemnumberFromBarcode( $item->{'barcode'} );
719         my $duplicate_itemnumber = exists( $item->{'itemnumber'} );
720
721         my $updsth = $dbh->prepare("UPDATE import_items SET status = ?, itemnumber = ? WHERE import_items_id = ?");
722         if ( $action eq "replace" && $duplicate_itemnumber ) {
723             # Duplicate itemnumbers have precedence, that way we can update barcodes by overlaying
724             ModItemFromMarc( $item_marc, $biblionumber, $item->{itemnumber} );
725             $updsth->bind_param( 1, 'imported' );
726             $updsth->bind_param( 2, $item->{itemnumber} );
727             $updsth->bind_param( 3, $row->{'import_items_id'} );
728             $updsth->execute();
729             $updsth->finish();
730             $num_items_replaced++;
731         } elsif ( $action eq "replace" && $duplicate_barcode ) {
732             my $itemnumber = GetItemnumberFromBarcode( $item->{'barcode'} );
733             ModItemFromMarc( $item_marc, $biblionumber, $itemnumber );
734             $updsth->bind_param( 1, 'imported' );
735             $updsth->bind_param( 2, $item->{itemnumber} );
736             $updsth->bind_param( 3, $row->{'import_items_id'} );
737             $updsth->execute();
738             $updsth->finish();
739             $num_items_replaced++;
740         } elsif ($duplicate_barcode) {
741             $updsth->bind_param( 1, 'error' );
742             $updsth->bind_param( 2, 'duplicate item barcode' );
743             $updsth->bind_param( 3, $row->{'import_items_id'} );
744             $updsth->execute();
745             $num_items_errored++;
746         } else {
747             my ( $item_biblionumber, $biblioitemnumber, $itemnumber ) = AddItemFromMarc( $item_marc, $biblionumber );
748             $updsth->bind_param( 1, 'imported' );
749             $updsth->bind_param( 2, $itemnumber );
750             $updsth->bind_param( 3, $row->{'import_items_id'} );
751             $updsth->execute();
752             $updsth->finish();
753             $num_items_added++;
754         }
755     }
756
757     return ( $num_items_added, $num_items_replaced, $num_items_errored );
758 }
759
760 =head2 BatchRevertRecords
761
762   my ($num_deleted, $num_errors, $num_reverted, $num_items_deleted, 
763       $num_ignored) = BatchRevertRecords($batch_id);
764
765 =cut
766
767 sub BatchRevertRecords {
768     my $batch_id = shift;
769
770     my $record_type;
771     my $num_deleted = 0;
772     my $num_errors = 0;
773     my $num_reverted = 0;
774     my $num_ignored = 0;
775     my $num_items_deleted = 0;
776     # commit (i.e., save, all records in the batch)
777     SetImportBatchStatus('reverting');
778     my $overlay_action = GetImportBatchOverlayAction($batch_id);
779     my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
780     my $dbh = C4::Context->dbh;
781     my $sth = $dbh->prepare("SELECT import_records.import_record_id, record_type, status, overlay_status, marcxml_old, encoding, matched_biblionumber, matched_authid
782                              FROM import_records
783                              LEFT JOIN import_auths ON (import_records.import_record_id=import_auths.import_record_id)
784                              LEFT JOIN import_biblios ON (import_records.import_record_id=import_biblios.import_record_id)
785                              WHERE import_batch_id = ?");
786     $sth->execute($batch_id);
787     my $marc_type;
788     my $marcflavour = C4::Context->preference('marcflavour');
789     while (my $rowref = $sth->fetchrow_hashref) {
790         $record_type = $rowref->{'record_type'};
791         if ($rowref->{'status'} eq 'error' or $rowref->{'status'} eq 'reverted') {
792             $num_ignored++;
793             next;
794         }
795         if ($marcflavour eq 'UNIMARC' && $record_type eq 'auth') {
796             $marc_type = 'UNIMARCAUTH';
797         } elsif ($marcflavour eq 'UNIMARC') {
798             $marc_type = 'UNIMARC';
799         } else {
800             $marc_type = 'USMARC';
801         }
802
803         my $record_result = _get_revert_action($overlay_action, $rowref->{'overlay_status'}, $rowref->{'status'});
804
805         if ($record_result eq 'delete') {
806             my $error = undef;
807             if  ($record_type eq 'biblio') {
808                 $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
809                 $error = DelBiblio($rowref->{'matched_biblionumber'});
810             } else {
811                 my $deletedauthid = DelAuthority($rowref->{'matched_authid'});
812             }
813             if (defined $error) {
814                 $num_errors++;
815             } else {
816                 $num_deleted++;
817                 SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
818             }
819         } elsif ($record_result eq 'restore') {
820             $num_reverted++;
821             my $old_record = MARC::Record->new_from_xml(StripNonXmlChars($rowref->{'marcxml_old'}), 'UTF-8', $rowref->{'encoding'}, $marc_type);
822             if ($record_type eq 'biblio') {
823                 my $biblionumber = $rowref->{'matched_biblionumber'};
824                 my ($count, $oldbiblio) = GetBiblio($biblionumber);
825                 $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
826                 ModBiblio($old_record, $biblionumber, $oldbiblio->{'frameworkcode'});
827             } else {
828                 my $authid = $rowref->{'matched_authid'};
829                 ModAuthority($authid, $old_record, GuessAuthTypeCode($old_record));
830             }
831             SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
832         } elsif ($record_result eq 'ignore') {
833             if ($record_type eq 'biblio') {
834                 $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
835             }
836             SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
837         }
838         my $query;
839         if ($record_type eq 'biblio') {
840             # remove matched_biblionumber only if there is no 'imported' item left
841             $query = "UPDATE import_biblios SET matched_biblionumber = NULL WHERE import_record_id = ?";
842             $query = "UPDATE import_biblios SET matched_biblionumber = NULL WHERE import_record_id = ?  AND NOT EXISTS (SELECT * FROM import_items WHERE import_items.import_record_id=import_biblios.import_record_id and status='imported')";
843         } else {
844             $query = "UPDATE import_auths SET matched_authid = NULL WHERE import_record_id = ?";
845         }
846         my $sth2 = $dbh->prepare_cached($query);
847         $sth2->execute($rowref->{'import_record_id'});
848     }
849
850     $sth->finish();
851     SetImportBatchStatus($batch_id, 'reverted');
852     return ($num_deleted, $num_errors, $num_reverted, $num_items_deleted, $num_ignored);
853 }
854
855 =head2 BatchRevertItems
856
857   my $num_items_deleted = BatchRevertItems($import_record_id, $biblionumber);
858
859 =cut
860
861 sub BatchRevertItems {
862     my ($import_record_id, $biblionumber) = @_;
863
864     my $dbh = C4::Context->dbh;
865     my $num_items_deleted = 0;
866
867     my $sth = $dbh->prepare_cached("SELECT import_items_id, itemnumber
868                                    FROM import_items
869                                    JOIN items USING (itemnumber)
870                                    WHERE import_record_id = ?");
871     $sth->bind_param(1, $import_record_id);
872     $sth->execute();
873     while (my $row = $sth->fetchrow_hashref()) {
874         my $error = DelItemCheck($dbh, $biblionumber, $row->{'itemnumber'});
875         if ($error == 1){
876             my $updsth = $dbh->prepare("UPDATE import_items SET status = ? WHERE import_items_id = ?");
877             $updsth->bind_param(1, 'reverted');
878             $updsth->bind_param(2, $row->{'import_items_id'});
879             $updsth->execute();
880             $updsth->finish();
881             $num_items_deleted++;
882         }
883         else {
884             next;
885         }
886     }
887     $sth->finish();
888     return $num_items_deleted;
889 }
890
891 =head2 CleanBatch
892
893   CleanBatch($batch_id)
894
895 Deletes all staged records from the import batch
896 and sets the status of the batch to 'cleaned'.  Note
897 that deleting a stage record does *not* affect
898 any record that has been committed to the database.
899
900 =cut
901
902 sub CleanBatch {
903     my $batch_id = shift;
904     return unless defined $batch_id;
905
906     C4::Context->dbh->do('DELETE FROM import_records WHERE import_batch_id = ?', {}, $batch_id);
907     SetImportBatchStatus($batch_id, 'cleaned');
908 }
909
910 =head2 GetAllImportBatches
911
912   my $results = GetAllImportBatches();
913
914 Returns a references to an array of hash references corresponding
915 to all import_batches rows (of batch_type 'batch'), sorted in 
916 ascending order by import_batch_id.
917
918 =cut
919
920 sub  GetAllImportBatches {
921     my $dbh = C4::Context->dbh;
922     my $sth = $dbh->prepare_cached("SELECT * FROM import_batches
923                                     WHERE batch_type IN ('batch', 'webservice')
924                                     ORDER BY import_batch_id ASC");
925
926     my $results = [];
927     $sth->execute();
928     while (my $row = $sth->fetchrow_hashref) {
929         push @$results, $row;
930     }
931     $sth->finish();
932     return $results;
933 }
934
935 =head2 GetStagedWebserviceBatches
936
937   my $batch_ids = GetStagedWebserviceBatches();
938
939 Returns a references to an array of batch id's
940 of batch_type 'webservice' that are not imported
941
942 =cut
943
944 my $PENDING_WEBSERVICE_BATCHES_QRY = <<EOQ;
945 SELECT import_batch_id FROM import_batches
946 WHERE batch_type = 'webservice'
947 AND import_status = 'staged'
948 EOQ
949 sub  GetStagedWebserviceBatches {
950     my $dbh = C4::Context->dbh;
951     return $dbh->selectcol_arrayref($PENDING_WEBSERVICE_BATCHES_QRY);
952 }
953
954 =head2 GetImportBatchRangeDesc
955
956   my $results = GetImportBatchRangeDesc($offset, $results_per_group);
957
958 Returns a reference to an array of hash references corresponding to
959 import_batches rows (sorted in descending order by import_batch_id)
960 start at the given offset.
961
962 =cut
963
964 sub GetImportBatchRangeDesc {
965     my ($offset, $results_per_group) = @_;
966
967     my $dbh = C4::Context->dbh;
968     my $query = "SELECT * FROM import_batches
969                                     WHERE batch_type IN ('batch', 'webservice')
970                                     ORDER BY import_batch_id DESC";
971     my @params;
972     if ($results_per_group){
973         $query .= " LIMIT ?";
974         push(@params, $results_per_group);
975     }
976     if ($offset){
977         $query .= " OFFSET ?";
978         push(@params, $offset);
979     }
980     my $sth = $dbh->prepare_cached($query);
981     $sth->execute(@params);
982     my $results = $sth->fetchall_arrayref({});
983     $sth->finish();
984     return $results;
985 }
986
987 =head2 GetItemNumbersFromImportBatch
988
989   my @itemsnos = GetItemNumbersFromImportBatch($batch_id);
990
991 =cut
992
993 sub GetItemNumbersFromImportBatch {
994         my ($batch_id) = @_;
995         my $dbh = C4::Context->dbh;
996         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=?");
997         $sth->execute($batch_id);
998         my @items ;
999         while ( my ($itm) = $sth->fetchrow_array ) {
1000                 push @items, $itm;
1001         }
1002         return @items;
1003 }
1004
1005 =head2 GetNumberOfImportBatches 
1006
1007   my $count = GetNumberOfImportBatches();
1008
1009 =cut
1010
1011 sub GetNumberOfNonZ3950ImportBatches {
1012     my $dbh = C4::Context->dbh;
1013     my $sth = $dbh->prepare("SELECT COUNT(*) FROM import_batches WHERE batch_type != 'z3950'");
1014     $sth->execute();
1015     my ($count) = $sth->fetchrow_array();
1016     $sth->finish();
1017     return $count;
1018 }
1019
1020 =head2 GetImportRecordsRange
1021
1022   my $results = GetImportRecordsRange($batch_id, $offset, $results_per_group);
1023
1024 Returns a reference to an array of hash references corresponding to
1025 import_biblios/import_auths/import_records rows for a given batch
1026 starting at the given offset.
1027
1028 =cut
1029
1030 sub GetImportRecordsRange {
1031     my ( $batch_id, $offset, $results_per_group, $status, $parameters ) = @_;
1032
1033     my $dbh = C4::Context->dbh;
1034
1035     my $order_by = $parameters->{order_by} || 'import_record_id';
1036     ( $order_by ) = grep( /^$order_by$/, qw( import_record_id title status overlay_status ) ) ? $order_by : 'import_record_id';
1037     $order_by .= ",authorized_heading" if $order_by eq 'title';
1038
1039     my $order_by_direction =
1040       uc( $parameters->{order_by_direction} ) eq 'DESC' ? 'DESC' : 'ASC';
1041
1042     my $query = "SELECT title, author, isbn, issn, authorized_heading, import_records.import_record_id,
1043                                            record_sequence, status, overlay_status,
1044                                            matched_biblionumber, matched_authid, record_type
1045                                     FROM   import_records
1046                                     LEFT JOIN import_auths ON (import_records.import_record_id=import_auths.import_record_id)
1047                                     LEFT JOIN import_biblios ON (import_records.import_record_id=import_biblios.import_record_id)
1048                                     WHERE  import_batch_id = ?";
1049     my @params;
1050     push(@params, $batch_id);
1051     if ($status) {
1052         $query .= " AND status=?";
1053         push(@params,$status);
1054     }
1055
1056     $query.=" ORDER BY $order_by $order_by_direction";
1057
1058     if($results_per_group){
1059         $query .= " LIMIT ?";
1060         push(@params, $results_per_group);
1061     }
1062     if($offset){
1063         $query .= " OFFSET ?";
1064         push(@params, $offset);
1065     }
1066     my $sth = $dbh->prepare_cached($query);
1067     $sth->execute(@params);
1068     my $results = $sth->fetchall_arrayref({});
1069     $sth->finish();
1070     return $results;
1071
1072 }
1073
1074 =head2 GetBestRecordMatch
1075
1076   my $record_id = GetBestRecordMatch($import_record_id);
1077
1078 =cut
1079
1080 sub GetBestRecordMatch {
1081     my ($import_record_id) = @_;
1082
1083     my $dbh = C4::Context->dbh;
1084     my $sth = $dbh->prepare("SELECT candidate_match_id
1085                              FROM   import_record_matches
1086                              JOIN   import_records ON ( import_record_matches.import_record_id = import_records.import_record_id )
1087                              LEFT JOIN biblio ON ( candidate_match_id = biblio.biblionumber )
1088                              LEFT JOIN auth_header ON ( candidate_match_id = auth_header.authid )
1089                              WHERE  import_record_matches.import_record_id = ? AND
1090                              (  (import_records.record_type = 'biblio' AND biblio.biblionumber IS NOT NULL) OR
1091                                 (import_records.record_type = 'auth' AND auth_header.authid IS NOT NULL) )
1092                              ORDER BY score DESC, candidate_match_id DESC");
1093     $sth->execute($import_record_id);
1094     my ($record_id) = $sth->fetchrow_array();
1095     $sth->finish();
1096     return $record_id;
1097 }
1098
1099 =head2 GetImportBatchStatus
1100
1101   my $status = GetImportBatchStatus($batch_id);
1102
1103 =cut
1104
1105 sub GetImportBatchStatus {
1106     my ($batch_id) = @_;
1107
1108     my $dbh = C4::Context->dbh;
1109     my $sth = $dbh->prepare("SELECT import_status FROM import_batches WHERE import_batch_id = ?");
1110     $sth->execute($batch_id);
1111     my ($status) = $sth->fetchrow_array();
1112     $sth->finish();
1113     return $status;
1114
1115 }
1116
1117 =head2 SetImportBatchStatus
1118
1119   SetImportBatchStatus($batch_id, $new_status);
1120
1121 =cut
1122
1123 sub SetImportBatchStatus {
1124     my ($batch_id, $new_status) = @_;
1125
1126     my $dbh = C4::Context->dbh;
1127     my $sth = $dbh->prepare("UPDATE import_batches SET import_status = ? WHERE import_batch_id = ?");
1128     $sth->execute($new_status, $batch_id);
1129     $sth->finish();
1130
1131 }
1132
1133 =head2 GetImportBatchOverlayAction
1134
1135   my $overlay_action = GetImportBatchOverlayAction($batch_id);
1136
1137 =cut
1138
1139 sub GetImportBatchOverlayAction {
1140     my ($batch_id) = @_;
1141
1142     my $dbh = C4::Context->dbh;
1143     my $sth = $dbh->prepare("SELECT overlay_action FROM import_batches WHERE import_batch_id = ?");
1144     $sth->execute($batch_id);
1145     my ($overlay_action) = $sth->fetchrow_array();
1146     $sth->finish();
1147     return $overlay_action;
1148
1149 }
1150
1151
1152 =head2 SetImportBatchOverlayAction
1153
1154   SetImportBatchOverlayAction($batch_id, $new_overlay_action);
1155
1156 =cut
1157
1158 sub SetImportBatchOverlayAction {
1159     my ($batch_id, $new_overlay_action) = @_;
1160
1161     my $dbh = C4::Context->dbh;
1162     my $sth = $dbh->prepare("UPDATE import_batches SET overlay_action = ? WHERE import_batch_id = ?");
1163     $sth->execute($new_overlay_action, $batch_id);
1164     $sth->finish();
1165
1166 }
1167
1168 =head2 GetImportBatchNoMatchAction
1169
1170   my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
1171
1172 =cut
1173
1174 sub GetImportBatchNoMatchAction {
1175     my ($batch_id) = @_;
1176
1177     my $dbh = C4::Context->dbh;
1178     my $sth = $dbh->prepare("SELECT nomatch_action FROM import_batches WHERE import_batch_id = ?");
1179     $sth->execute($batch_id);
1180     my ($nomatch_action) = $sth->fetchrow_array();
1181     $sth->finish();
1182     return $nomatch_action;
1183
1184 }
1185
1186
1187 =head2 SetImportBatchNoMatchAction
1188
1189   SetImportBatchNoMatchAction($batch_id, $new_nomatch_action);
1190
1191 =cut
1192
1193 sub SetImportBatchNoMatchAction {
1194     my ($batch_id, $new_nomatch_action) = @_;
1195
1196     my $dbh = C4::Context->dbh;
1197     my $sth = $dbh->prepare("UPDATE import_batches SET nomatch_action = ? WHERE import_batch_id = ?");
1198     $sth->execute($new_nomatch_action, $batch_id);
1199     $sth->finish();
1200
1201 }
1202
1203 =head2 GetImportBatchItemAction
1204
1205   my $item_action = GetImportBatchItemAction($batch_id);
1206
1207 =cut
1208
1209 sub GetImportBatchItemAction {
1210     my ($batch_id) = @_;
1211
1212     my $dbh = C4::Context->dbh;
1213     my $sth = $dbh->prepare("SELECT item_action FROM import_batches WHERE import_batch_id = ?");
1214     $sth->execute($batch_id);
1215     my ($item_action) = $sth->fetchrow_array();
1216     $sth->finish();
1217     return $item_action;
1218
1219 }
1220
1221
1222 =head2 SetImportBatchItemAction
1223
1224   SetImportBatchItemAction($batch_id, $new_item_action);
1225
1226 =cut
1227
1228 sub SetImportBatchItemAction {
1229     my ($batch_id, $new_item_action) = @_;
1230
1231     my $dbh = C4::Context->dbh;
1232     my $sth = $dbh->prepare("UPDATE import_batches SET item_action = ? WHERE import_batch_id = ?");
1233     $sth->execute($new_item_action, $batch_id);
1234     $sth->finish();
1235
1236 }
1237
1238 =head2 GetImportBatchMatcher
1239
1240   my $matcher_id = GetImportBatchMatcher($batch_id);
1241
1242 =cut
1243
1244 sub GetImportBatchMatcher {
1245     my ($batch_id) = @_;
1246
1247     my $dbh = C4::Context->dbh;
1248     my $sth = $dbh->prepare("SELECT matcher_id FROM import_batches WHERE import_batch_id = ?");
1249     $sth->execute($batch_id);
1250     my ($matcher_id) = $sth->fetchrow_array();
1251     $sth->finish();
1252     return $matcher_id;
1253
1254 }
1255
1256
1257 =head2 SetImportBatchMatcher
1258
1259   SetImportBatchMatcher($batch_id, $new_matcher_id);
1260
1261 =cut
1262
1263 sub SetImportBatchMatcher {
1264     my ($batch_id, $new_matcher_id) = @_;
1265
1266     my $dbh = C4::Context->dbh;
1267     my $sth = $dbh->prepare("UPDATE import_batches SET matcher_id = ? WHERE import_batch_id = ?");
1268     $sth->execute($new_matcher_id, $batch_id);
1269     $sth->finish();
1270
1271 }
1272
1273 =head2 GetImportRecordOverlayStatus
1274
1275   my $overlay_status = GetImportRecordOverlayStatus($import_record_id);
1276
1277 =cut
1278
1279 sub GetImportRecordOverlayStatus {
1280     my ($import_record_id) = @_;
1281
1282     my $dbh = C4::Context->dbh;
1283     my $sth = $dbh->prepare("SELECT overlay_status FROM import_records WHERE import_record_id = ?");
1284     $sth->execute($import_record_id);
1285     my ($overlay_status) = $sth->fetchrow_array();
1286     $sth->finish();
1287     return $overlay_status;
1288
1289 }
1290
1291
1292 =head2 SetImportRecordOverlayStatus
1293
1294   SetImportRecordOverlayStatus($import_record_id, $new_overlay_status);
1295
1296 =cut
1297
1298 sub SetImportRecordOverlayStatus {
1299     my ($import_record_id, $new_overlay_status) = @_;
1300
1301     my $dbh = C4::Context->dbh;
1302     my $sth = $dbh->prepare("UPDATE import_records SET overlay_status = ? WHERE import_record_id = ?");
1303     $sth->execute($new_overlay_status, $import_record_id);
1304     $sth->finish();
1305
1306 }
1307
1308 =head2 GetImportRecordStatus
1309
1310   my $overlay_status = GetImportRecordStatus($import_record_id);
1311
1312 =cut
1313
1314 sub GetImportRecordStatus {
1315     my ($import_record_id) = @_;
1316
1317     my $dbh = C4::Context->dbh;
1318     my $sth = $dbh->prepare("SELECT status FROM import_records WHERE import_record_id = ?");
1319     $sth->execute($import_record_id);
1320     my ($overlay_status) = $sth->fetchrow_array();
1321     $sth->finish();
1322     return $overlay_status;
1323
1324 }
1325
1326
1327 =head2 SetImportRecordStatus
1328
1329   SetImportRecordStatus($import_record_id, $new_overlay_status);
1330
1331 =cut
1332
1333 sub SetImportRecordStatus {
1334     my ($import_record_id, $new_overlay_status) = @_;
1335
1336     my $dbh = C4::Context->dbh;
1337     my $sth = $dbh->prepare("UPDATE import_records SET status = ? WHERE import_record_id = ?");
1338     $sth->execute($new_overlay_status, $import_record_id);
1339     $sth->finish();
1340
1341 }
1342
1343 =head2 GetImportRecordMatches
1344
1345   my $results = GetImportRecordMatches($import_record_id, $best_only);
1346
1347 =cut
1348
1349 sub GetImportRecordMatches {
1350     my $import_record_id = shift;
1351     my $best_only = @_ ? shift : 0;
1352
1353     my $dbh = C4::Context->dbh;
1354     # FIXME currently biblio only
1355     my $sth = $dbh->prepare_cached("SELECT title, author, biblionumber,
1356                                     candidate_match_id, score, record_type
1357                                     FROM import_records
1358                                     JOIN import_record_matches USING (import_record_id)
1359                                     LEFT JOIN biblio ON (biblionumber = candidate_match_id)
1360                                     WHERE import_record_id = ?
1361                                     ORDER BY score DESC, biblionumber DESC");
1362     $sth->bind_param(1, $import_record_id);
1363     my $results = [];
1364     $sth->execute();
1365     while (my $row = $sth->fetchrow_hashref) {
1366         if ($row->{'record_type'} eq 'auth') {
1367             $row->{'authorized_heading'} = C4::AuthoritiesMarc::GetAuthorizedHeading( { authid => $row->{'candidate_match_id'} } );
1368         }
1369         next if ($row->{'record_type'} eq 'biblio' && not $row->{'biblionumber'});
1370         push @$results, $row;
1371         last if $best_only;
1372     }
1373     $sth->finish();
1374
1375     return $results;
1376     
1377 }
1378
1379
1380 =head2 SetImportRecordMatches
1381
1382   SetImportRecordMatches($import_record_id, @matches);
1383
1384 =cut
1385
1386 sub SetImportRecordMatches {
1387     my $import_record_id = shift;
1388     my @matches = @_;
1389
1390     my $dbh = C4::Context->dbh;
1391     my $delsth = $dbh->prepare("DELETE FROM import_record_matches WHERE import_record_id = ?");
1392     $delsth->execute($import_record_id);
1393     $delsth->finish();
1394
1395     my $sth = $dbh->prepare("INSERT INTO import_record_matches (import_record_id, candidate_match_id, score)
1396                                     VALUES (?, ?, ?)");
1397     foreach my $match (@matches) {
1398         $sth->execute($import_record_id, $match->{'record_id'}, $match->{'score'});
1399     }
1400 }
1401
1402
1403 # internal functions
1404
1405 sub _create_import_record {
1406     my ($batch_id, $record_sequence, $marc_record, $record_type, $encoding, $z3950random, $marc_type) = @_;
1407
1408     my $dbh = C4::Context->dbh;
1409     my $sth = $dbh->prepare("INSERT INTO import_records (import_batch_id, record_sequence, marc, marcxml, 
1410                                                          record_type, encoding, z3950random)
1411                                     VALUES (?, ?, ?, ?, ?, ?, ?)");
1412     $sth->execute($batch_id, $record_sequence, $marc_record->as_usmarc(), $marc_record->as_xml($marc_type),
1413                   $record_type, $encoding, $z3950random);
1414     my $import_record_id = $dbh->{'mysql_insertid'};
1415     $sth->finish();
1416     return $import_record_id;
1417 }
1418
1419 sub _update_import_record_marc {
1420     my ($import_record_id, $marc_record, $marc_type) = @_;
1421
1422     my $dbh = C4::Context->dbh;
1423     my $sth = $dbh->prepare("UPDATE import_records SET marc = ?, marcxml = ?
1424                              WHERE  import_record_id = ?");
1425     $sth->execute($marc_record->as_usmarc(), $marc_record->as_xml($marc_type), $import_record_id);
1426     $sth->finish();
1427 }
1428
1429 sub _add_auth_fields {
1430     my ($import_record_id, $marc_record) = @_;
1431
1432     my $controlnumber;
1433     if ($marc_record->field('001')) {
1434         $controlnumber = $marc_record->field('001')->data();
1435     }
1436     my $authorized_heading = C4::AuthoritiesMarc::GetAuthorizedHeading({ record => $marc_record });
1437     my $dbh = C4::Context->dbh;
1438     my $sth = $dbh->prepare("INSERT INTO import_auths (import_record_id, control_number, authorized_heading) VALUES (?, ?, ?)");
1439     $sth->execute($import_record_id, $controlnumber, $authorized_heading);
1440     $sth->finish();
1441 }
1442
1443 sub _add_biblio_fields {
1444     my ($import_record_id, $marc_record) = @_;
1445
1446     my ($title, $author, $isbn, $issn) = _parse_biblio_fields($marc_record);
1447     my $dbh = C4::Context->dbh;
1448     # FIXME no controlnumber, originalsource
1449     $isbn = C4::Koha::_isbn_cleanup($isbn); # FIXME C4::Koha::_isbn_cleanup should be made public
1450     my $sth = $dbh->prepare("INSERT INTO import_biblios (import_record_id, title, author, isbn, issn) VALUES (?, ?, ?, ?, ?)");
1451     $sth->execute($import_record_id, $title, $author, $isbn, $issn);
1452     $sth->finish();
1453                 
1454 }
1455
1456 sub _update_biblio_fields {
1457     my ($import_record_id, $marc_record) = @_;
1458
1459     my ($title, $author, $isbn, $issn) = _parse_biblio_fields($marc_record);
1460     my $dbh = C4::Context->dbh;
1461     # FIXME no controlnumber, originalsource
1462     # FIXME 2 - should regularize normalization of ISBN wherever it is done
1463     $isbn =~ s/\(.*$//;
1464     $isbn =~ tr/ -_//;
1465     $isbn = uc $isbn;
1466     my $sth = $dbh->prepare("UPDATE import_biblios SET title = ?, author = ?, isbn = ?, issn = ?
1467                              WHERE  import_record_id = ?");
1468     $sth->execute($title, $author, $isbn, $issn, $import_record_id);
1469     $sth->finish();
1470 }
1471
1472 sub _parse_biblio_fields {
1473     my ($marc_record) = @_;
1474
1475     my $dbh = C4::Context->dbh;
1476     my $bibliofields = TransformMarcToKoha($dbh, $marc_record, '');
1477     return ($bibliofields->{'title'}, $bibliofields->{'author'}, $bibliofields->{'isbn'}, $bibliofields->{'issn'});
1478
1479 }
1480
1481 sub _update_batch_record_counts {
1482     my ($batch_id) = @_;
1483
1484     my $dbh = C4::Context->dbh;
1485     my $sth = $dbh->prepare_cached("UPDATE import_batches SET
1486                                         num_records = (
1487                                             SELECT COUNT(*)
1488                                             FROM import_records
1489                                             WHERE import_batch_id = import_batches.import_batch_id),
1490                                         num_items = (
1491                                             SELECT COUNT(*)
1492                                             FROM import_records
1493                                             JOIN import_items USING (import_record_id)
1494                                             WHERE import_batch_id = import_batches.import_batch_id
1495                                             AND record_type = 'biblio')
1496                                     WHERE import_batch_id = ?");
1497     $sth->bind_param(1, $batch_id);
1498     $sth->execute();
1499     $sth->finish();
1500 }
1501
1502 sub _get_commit_action {
1503     my ($overlay_action, $nomatch_action, $item_action, $overlay_status, $import_record_id, $record_type) = @_;
1504     
1505     if ($record_type eq 'biblio') {
1506         my ($bib_result, $bib_match, $item_result);
1507
1508         if ($overlay_status ne 'no_match') {
1509             $bib_match = GetBestRecordMatch($import_record_id);
1510             if ($overlay_action eq 'replace') {
1511                 $bib_result  = defined($bib_match) ? 'replace' : 'create_new';
1512             } elsif ($overlay_action eq 'create_new') {
1513                 $bib_result  = 'create_new';
1514             } elsif ($overlay_action eq 'ignore') {
1515                 $bib_result  = 'ignore';
1516             }
1517          if($item_action eq 'always_add' or $item_action eq 'add_only_for_matches'){
1518                 $item_result = 'create_new';
1519        }
1520       elsif($item_action eq 'replace'){
1521           $item_result = 'replace';
1522           }
1523       else {
1524              $item_result = 'ignore';
1525            }
1526         } else {
1527             $bib_result = $nomatch_action;
1528             $item_result = ($item_action eq 'always_add' or $item_action eq 'add_only_for_new')     ? 'create_new' : 'ignore';
1529         }
1530         return ($bib_result, $item_result, $bib_match);
1531     } else { # must be auths
1532         my ($auth_result, $auth_match);
1533
1534         if ($overlay_status ne 'no_match') {
1535             $auth_match = GetBestRecordMatch($import_record_id);
1536             if ($overlay_action eq 'replace') {
1537                 $auth_result  = defined($auth_match) ? 'replace' : 'create_new';
1538             } elsif ($overlay_action eq 'create_new') {
1539                 $auth_result  = 'create_new';
1540             } elsif ($overlay_action eq 'ignore') {
1541                 $auth_result  = 'ignore';
1542             }
1543         } else {
1544             $auth_result = $nomatch_action;
1545         }
1546
1547         return ($auth_result, undef, $auth_match);
1548
1549     }
1550 }
1551
1552 sub _get_revert_action {
1553     my ($overlay_action, $overlay_status, $status) = @_;
1554
1555     my $bib_result;
1556
1557     if ($status eq 'ignored') {
1558         $bib_result = 'ignore';
1559     } else {
1560         if ($overlay_action eq 'create_new') {
1561             $bib_result = 'delete';
1562         } else {
1563             $bib_result = ($overlay_status eq 'match_applied') ? 'restore' : 'delete';
1564         }
1565     }
1566     return $bib_result;
1567 }
1568
1569 1;
1570 __END__
1571
1572 =head1 AUTHOR
1573
1574 Koha Development Team <http://koha-community.org/>
1575
1576 Galen Charlton <galen.charlton@liblime.com>
1577
1578 =cut