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