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