Bug 8435: DBRev 3.13.00.038
[koha.git] / C4 / ImportBatch.pm
1 package C4::ImportBatch;
2
3 # Copyright (C) 2007 LibLime, 2012 C & P Bibliography Services
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
10 # version.
11 #
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along
17 # with Koha; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20 use strict;
21 use warnings;
22
23 use C4::Context;
24 use C4::Koha;
25 use C4::Biblio;
26 use C4::Items;
27 use C4::Charset;
28 use C4::AuthoritiesMarc;
29
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 = 0;
692     my $num_items_errored = 0;
693     my $num_items_replaced = 0;
694
695     my $sth = $dbh->prepare( "
696         SELECT import_items_id, import_items.marcxml, encoding
697         FROM import_items
698         JOIN import_records USING (import_record_id)
699         WHERE import_record_id = ?
700         ORDER BY import_items_id
701     " );
702     $sth->bind_param( 1, $import_record_id );
703     $sth->execute();
704
705     while ( my $row = $sth->fetchrow_hashref() ) {
706         my $item_marc = MARC::Record->new_from_xml( StripNonXmlChars( $row->{'marcxml'} ), 'UTF-8', $row->{'encoding'} );
707
708         # Delete date_due subfield as to not accidentally delete item checkout due dates
709         my ( $MARCfield, $MARCsubfield ) = GetMarcFromKohaField( 'items.onloan', GetFrameworkCode($biblionumber) );
710         $item_marc->field($MARCfield)->delete_subfield( code => $MARCsubfield );
711
712         my $item = TransformMarcToKoha( $dbh, $item_marc );
713
714         my $duplicate_barcode = exists( $item->{'barcode'} ) && GetItemnumberFromBarcode( $item->{'barcode'} );
715         my $duplicate_itemnumber = exists( $item->{'itemnumber'} );
716
717         my $updsth = $dbh->prepare("UPDATE import_items SET status = ?, itemnumber = ? WHERE import_items_id = ?");
718         if ( $action eq "replace" && $duplicate_itemnumber ) {
719             # Duplicate itemnumbers have precedence, that way we can update barcodes by overlaying
720             ModItemFromMarc( $item_marc, $biblionumber, $item->{itemnumber} );
721             $updsth->bind_param( 1, 'imported' );
722             $updsth->bind_param( 2, $item->{itemnumber} );
723             $updsth->bind_param( 3, $row->{'import_items_id'} );
724             $updsth->execute();
725             $updsth->finish();
726             $num_items_replaced++;
727         } elsif ( $action eq "replace" && $duplicate_barcode ) {
728             my $itemnumber = GetItemnumberFromBarcode( $item->{'barcode'} );
729             ModItemFromMarc( $item_marc, $biblionumber, $itemnumber );
730             $updsth->bind_param( 1, 'imported' );
731             $updsth->bind_param( 2, $item->{itemnumber} );
732             $updsth->bind_param( 3, $row->{'import_items_id'} );
733             $updsth->execute();
734             $updsth->finish();
735             $num_items_replaced++;
736         } elsif ($duplicate_barcode) {
737             $updsth->bind_param( 1, 'error' );
738             $updsth->bind_param( 2, 'duplicate item barcode' );
739             $updsth->bind_param( 3, $row->{'import_items_id'} );
740             $updsth->execute();
741             $num_items_errored++;
742         } else {
743             my ( $item_biblionumber, $biblioitemnumber, $itemnumber ) = AddItemFromMarc( $item_marc, $biblionumber );
744             $updsth->bind_param( 1, 'imported' );
745             $updsth->bind_param( 2, $itemnumber );
746             $updsth->bind_param( 3, $row->{'import_items_id'} );
747             $updsth->execute();
748             $updsth->finish();
749             $num_items_added++;
750         }
751     }
752
753     return ( $num_items_added, $num_items_replaced, $num_items_errored );
754 }
755
756 =head2 BatchRevertRecords
757
758   my ($num_deleted, $num_errors, $num_reverted, $num_items_deleted, 
759       $num_ignored) = BatchRevertRecords($batch_id);
760
761 =cut
762
763 sub BatchRevertRecords {
764     my $batch_id = shift;
765
766     my $record_type;
767     my $num_deleted = 0;
768     my $num_errors = 0;
769     my $num_reverted = 0;
770     my $num_ignored = 0;
771     my $num_items_deleted = 0;
772     # commit (i.e., save, all records in the batch)
773     SetImportBatchStatus('reverting');
774     my $overlay_action = GetImportBatchOverlayAction($batch_id);
775     my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
776     my $dbh = C4::Context->dbh;
777     my $sth = $dbh->prepare("SELECT import_records.import_record_id, record_type, status, overlay_status, marcxml_old, encoding, matched_biblionumber, matched_authid
778                              FROM import_records
779                              LEFT JOIN import_auths ON (import_records.import_record_id=import_auths.import_record_id)
780                              LEFT JOIN import_biblios ON (import_records.import_record_id=import_biblios.import_record_id)
781                              WHERE import_batch_id = ?");
782     $sth->execute($batch_id);
783     my $marc_type;
784     my $marcflavour = C4::Context->preference('marcflavour');
785     while (my $rowref = $sth->fetchrow_hashref) {
786         $record_type = $rowref->{'record_type'};
787         if ($rowref->{'status'} eq 'error' or $rowref->{'status'} eq 'reverted') {
788             $num_ignored++;
789             next;
790         }
791         if ($marcflavour eq 'UNIMARC' && $record_type eq 'auth') {
792             $marc_type = 'UNIMARCAUTH';
793         } elsif ($marcflavour eq 'UNIMARC') {
794             $marc_type = 'UNIMARC';
795         } else {
796             $marc_type = 'USMARC';
797         }
798
799         my $record_result = _get_revert_action($overlay_action, $rowref->{'overlay_status'}, $rowref->{'status'});
800
801         if ($record_result eq 'delete') {
802             my $error = undef;
803             if  ($record_type eq 'biblio') {
804                 $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
805                 $error = DelBiblio($rowref->{'matched_biblionumber'});
806             } else {
807                 my $deletedauthid = DelAuthority($rowref->{'matched_authid'});
808             }
809             if (defined $error) {
810                 $num_errors++;
811             } else {
812                 $num_deleted++;
813                 SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
814             }
815         } elsif ($record_result eq 'restore') {
816             $num_reverted++;
817             my $old_record = MARC::Record->new_from_xml(StripNonXmlChars($rowref->{'marcxml_old'}), 'UTF-8', $rowref->{'encoding'}, $marc_type);
818             if ($record_type eq 'biblio') {
819                 my $biblionumber = $rowref->{'matched_biblionumber'};
820                 my ($count, $oldbiblio) = GetBiblio($biblionumber);
821                 $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
822                 ModBiblio($old_record, $biblionumber, $oldbiblio->{'frameworkcode'});
823             } else {
824                 my $authid = $rowref->{'matched_authid'};
825                 ModAuthority($authid, $old_record, GuessAuthTypeCode($old_record));
826             }
827             SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
828         } elsif ($record_result eq 'ignore') {
829             if ($record_type eq 'biblio') {
830                 $num_items_deleted += BatchRevertItems($rowref->{'import_record_id'}, $rowref->{'matched_biblionumber'});
831             }
832             SetImportRecordStatus($rowref->{'import_record_id'}, 'reverted');
833         }
834         my $query;
835         if ($record_type eq 'biblio') {
836             # remove matched_biblionumber only if there is no 'imported' item left
837             $query = "UPDATE import_biblios SET matched_biblionumber = NULL WHERE import_record_id = ?";
838             $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')";
839         } else {
840             $query = "UPDATE import_auths SET matched_authid = NULL WHERE import_record_id = ?";
841         }
842         my $sth2 = $dbh->prepare_cached($query);
843         $sth2->execute($rowref->{'import_record_id'});
844     }
845
846     $sth->finish();
847     SetImportBatchStatus($batch_id, 'reverted');
848     return ($num_deleted, $num_errors, $num_reverted, $num_items_deleted, $num_ignored);
849 }
850
851 =head2 BatchRevertItems
852
853   my $num_items_deleted = BatchRevertItems($import_record_id, $biblionumber);
854
855 =cut
856
857 sub BatchRevertItems {
858     my ($import_record_id, $biblionumber) = @_;
859
860     my $dbh = C4::Context->dbh;
861     my $num_items_deleted = 0;
862
863     my $sth = $dbh->prepare_cached("SELECT import_items_id, itemnumber
864                                    FROM import_items
865                                    JOIN items USING (itemnumber)
866                                    WHERE import_record_id = ?");
867     $sth->bind_param(1, $import_record_id);
868     $sth->execute();
869     while (my $row = $sth->fetchrow_hashref()) {
870         my $error = DelItemCheck($dbh, $biblionumber, $row->{'itemnumber'});
871         if ($error == 1){
872             my $updsth = $dbh->prepare("UPDATE import_items SET status = ? WHERE import_items_id = ?");
873             $updsth->bind_param(1, 'reverted');
874             $updsth->bind_param(2, $row->{'import_items_id'});
875             $updsth->execute();
876             $updsth->finish();
877             $num_items_deleted++;
878         }
879         else {
880             next;
881         }
882     }
883     $sth->finish();
884     return $num_items_deleted;
885 }
886
887 =head2 CleanBatch
888
889   CleanBatch($batch_id)
890
891 Deletes all staged records from the import batch
892 and sets the status of the batch to 'cleaned'.  Note
893 that deleting a stage record does *not* affect
894 any record that has been committed to the database.
895
896 =cut
897
898 sub CleanBatch {
899     my $batch_id = shift;
900     return unless defined $batch_id;
901
902     C4::Context->dbh->do('DELETE FROM import_records WHERE import_batch_id = ?', {}, $batch_id);
903     SetImportBatchStatus($batch_id, 'cleaned');
904 }
905
906 =head2 GetAllImportBatches
907
908   my $results = GetAllImportBatches();
909
910 Returns a references to an array of hash references corresponding
911 to all import_batches rows (of batch_type 'batch'), sorted in 
912 ascending order by import_batch_id.
913
914 =cut
915
916 sub  GetAllImportBatches {
917     my $dbh = C4::Context->dbh;
918     my $sth = $dbh->prepare_cached("SELECT * FROM import_batches
919                                     WHERE batch_type IN ('batch', 'webservice')
920                                     ORDER BY import_batch_id ASC");
921
922     my $results = [];
923     $sth->execute();
924     while (my $row = $sth->fetchrow_hashref) {
925         push @$results, $row;
926     }
927     $sth->finish();
928     return $results;
929 }
930
931 =head2 GetStagedWebserviceBatches
932
933   my $batch_ids = GetStagedWebserviceBatches();
934
935 Returns a references to an array of batch id's
936 of batch_type 'webservice' that are not imported
937
938 =cut
939
940 my $PENDING_WEBSERVICE_BATCHES_QRY = <<EOQ;
941 SELECT import_batch_id FROM import_batches
942 WHERE batch_type = 'webservice'
943 AND import_status = 'staged'
944 EOQ
945 sub  GetStagedWebserviceBatches {
946     my $dbh = C4::Context->dbh;
947     return $dbh->selectcol_arrayref($PENDING_WEBSERVICE_BATCHES_QRY);
948 }
949
950 =head2 GetImportBatchRangeDesc
951
952   my $results = GetImportBatchRangeDesc($offset, $results_per_group);
953
954 Returns a reference to an array of hash references corresponding to
955 import_batches rows (sorted in descending order by import_batch_id)
956 start at the given offset.
957
958 =cut
959
960 sub GetImportBatchRangeDesc {
961     my ($offset, $results_per_group) = @_;
962
963     my $dbh = C4::Context->dbh;
964     my $query = "SELECT * FROM import_batches
965                                     WHERE batch_type IN ('batch', 'webservice')
966                                     ORDER BY import_batch_id DESC";
967     my @params;
968     if ($results_per_group){
969         $query .= " LIMIT ?";
970         push(@params, $results_per_group);
971     }
972     if ($offset){
973         $query .= " OFFSET ?";
974         push(@params, $offset);
975     }
976     my $sth = $dbh->prepare_cached($query);
977     $sth->execute(@params);
978     my $results = $sth->fetchall_arrayref({});
979     $sth->finish();
980     return $results;
981 }
982
983 =head2 GetItemNumbersFromImportBatch
984
985   my @itemsnos = GetItemNumbersFromImportBatch($batch_id);
986
987 =cut
988
989 sub GetItemNumbersFromImportBatch {
990         my ($batch_id) = @_;
991         my $dbh = C4::Context->dbh;
992         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=?");
993         $sth->execute($batch_id);
994         my @items ;
995         while ( my ($itm) = $sth->fetchrow_array ) {
996                 push @items, $itm;
997         }
998         return @items;
999 }
1000
1001 =head2 GetNumberOfImportBatches 
1002
1003   my $count = GetNumberOfImportBatches();
1004
1005 =cut
1006
1007 sub GetNumberOfNonZ3950ImportBatches {
1008     my $dbh = C4::Context->dbh;
1009     my $sth = $dbh->prepare("SELECT COUNT(*) FROM import_batches WHERE batch_type != 'z3950'");
1010     $sth->execute();
1011     my ($count) = $sth->fetchrow_array();
1012     $sth->finish();
1013     return $count;
1014 }
1015
1016 =head2 GetImportRecordsRange
1017
1018   my $results = GetImportRecordsRange($batch_id, $offset, $results_per_group);
1019
1020 Returns a reference to an array of hash references corresponding to
1021 import_biblios/import_auths/import_records rows for a given batch
1022 starting at the given offset.
1023
1024 =cut
1025
1026 sub GetImportRecordsRange {
1027     my ($batch_id, $offset, $results_per_group, $status) = @_;
1028
1029     my $dbh = C4::Context->dbh;
1030     my $query = "SELECT title, author, isbn, issn, authorized_heading, import_records.import_record_id,
1031                                            record_sequence, status, overlay_status,
1032                                            matched_biblionumber, matched_authid, record_type
1033                                     FROM   import_records
1034                                     LEFT JOIN import_auths ON (import_records.import_record_id=import_auths.import_record_id)
1035                                     LEFT JOIN import_biblios ON (import_records.import_record_id=import_biblios.import_record_id)
1036                                     WHERE  import_batch_id = ?";
1037     my @params;
1038     push(@params, $batch_id);
1039     if ($status) {
1040         $query .= " AND status=?";
1041         push(@params,$status);
1042     }
1043     $query.=" ORDER BY import_record_id";
1044
1045     if($results_per_group){
1046         $query .= " LIMIT ?";
1047         push(@params, $results_per_group);
1048     }
1049     if($offset){
1050         $query .= " OFFSET ?";
1051         push(@params, $offset);
1052     }
1053     my $sth = $dbh->prepare_cached($query);
1054     $sth->execute(@params);
1055     my $results = $sth->fetchall_arrayref({});
1056     $sth->finish();
1057     return $results;
1058
1059 }
1060
1061 =head2 GetBestRecordMatch
1062
1063   my $record_id = GetBestRecordMatch($import_record_id);
1064
1065 =cut
1066
1067 sub GetBestRecordMatch {
1068     my ($import_record_id) = @_;
1069
1070     my $dbh = C4::Context->dbh;
1071     my $sth = $dbh->prepare("SELECT candidate_match_id
1072                              FROM   import_record_matches
1073                              JOIN   import_records ON ( import_record_matches.import_record_id = import_records.import_record_id )
1074                              LEFT JOIN biblio ON ( candidate_match_id = biblio.biblionumber )
1075                              LEFT JOIN auth_header ON ( candidate_match_id = auth_header.authid )
1076                              WHERE  import_record_matches.import_record_id = ? AND
1077                              (  (import_records.record_type = 'biblio' AND biblio.biblionumber IS NOT NULL) OR
1078                                 (import_records.record_type = 'auth' AND auth_header.authid IS NOT NULL) )
1079                              ORDER BY score DESC, candidate_match_id DESC");
1080     $sth->execute($import_record_id);
1081     my ($record_id) = $sth->fetchrow_array();
1082     $sth->finish();
1083     return $record_id;
1084 }
1085
1086 =head2 GetImportBatchStatus
1087
1088   my $status = GetImportBatchStatus($batch_id);
1089
1090 =cut
1091
1092 sub GetImportBatchStatus {
1093     my ($batch_id) = @_;
1094
1095     my $dbh = C4::Context->dbh;
1096     my $sth = $dbh->prepare("SELECT import_status FROM import_batches WHERE import_batch_id = ?");
1097     $sth->execute($batch_id);
1098     my ($status) = $sth->fetchrow_array();
1099     $sth->finish();
1100     return $status;
1101
1102 }
1103
1104 =head2 SetImportBatchStatus
1105
1106   SetImportBatchStatus($batch_id, $new_status);
1107
1108 =cut
1109
1110 sub SetImportBatchStatus {
1111     my ($batch_id, $new_status) = @_;
1112
1113     my $dbh = C4::Context->dbh;
1114     my $sth = $dbh->prepare("UPDATE import_batches SET import_status = ? WHERE import_batch_id = ?");
1115     $sth->execute($new_status, $batch_id);
1116     $sth->finish();
1117
1118 }
1119
1120 =head2 GetImportBatchOverlayAction
1121
1122   my $overlay_action = GetImportBatchOverlayAction($batch_id);
1123
1124 =cut
1125
1126 sub GetImportBatchOverlayAction {
1127     my ($batch_id) = @_;
1128
1129     my $dbh = C4::Context->dbh;
1130     my $sth = $dbh->prepare("SELECT overlay_action FROM import_batches WHERE import_batch_id = ?");
1131     $sth->execute($batch_id);
1132     my ($overlay_action) = $sth->fetchrow_array();
1133     $sth->finish();
1134     return $overlay_action;
1135
1136 }
1137
1138
1139 =head2 SetImportBatchOverlayAction
1140
1141   SetImportBatchOverlayAction($batch_id, $new_overlay_action);
1142
1143 =cut
1144
1145 sub SetImportBatchOverlayAction {
1146     my ($batch_id, $new_overlay_action) = @_;
1147
1148     my $dbh = C4::Context->dbh;
1149     my $sth = $dbh->prepare("UPDATE import_batches SET overlay_action = ? WHERE import_batch_id = ?");
1150     $sth->execute($new_overlay_action, $batch_id);
1151     $sth->finish();
1152
1153 }
1154
1155 =head2 GetImportBatchNoMatchAction
1156
1157   my $nomatch_action = GetImportBatchNoMatchAction($batch_id);
1158
1159 =cut
1160
1161 sub GetImportBatchNoMatchAction {
1162     my ($batch_id) = @_;
1163
1164     my $dbh = C4::Context->dbh;
1165     my $sth = $dbh->prepare("SELECT nomatch_action FROM import_batches WHERE import_batch_id = ?");
1166     $sth->execute($batch_id);
1167     my ($nomatch_action) = $sth->fetchrow_array();
1168     $sth->finish();
1169     return $nomatch_action;
1170
1171 }
1172
1173
1174 =head2 SetImportBatchNoMatchAction
1175
1176   SetImportBatchNoMatchAction($batch_id, $new_nomatch_action);
1177
1178 =cut
1179
1180 sub SetImportBatchNoMatchAction {
1181     my ($batch_id, $new_nomatch_action) = @_;
1182
1183     my $dbh = C4::Context->dbh;
1184     my $sth = $dbh->prepare("UPDATE import_batches SET nomatch_action = ? WHERE import_batch_id = ?");
1185     $sth->execute($new_nomatch_action, $batch_id);
1186     $sth->finish();
1187
1188 }
1189
1190 =head2 GetImportBatchItemAction
1191
1192   my $item_action = GetImportBatchItemAction($batch_id);
1193
1194 =cut
1195
1196 sub GetImportBatchItemAction {
1197     my ($batch_id) = @_;
1198
1199     my $dbh = C4::Context->dbh;
1200     my $sth = $dbh->prepare("SELECT item_action FROM import_batches WHERE import_batch_id = ?");
1201     $sth->execute($batch_id);
1202     my ($item_action) = $sth->fetchrow_array();
1203     $sth->finish();
1204     return $item_action;
1205
1206 }
1207
1208
1209 =head2 SetImportBatchItemAction
1210
1211   SetImportBatchItemAction($batch_id, $new_item_action);
1212
1213 =cut
1214
1215 sub SetImportBatchItemAction {
1216     my ($batch_id, $new_item_action) = @_;
1217
1218     my $dbh = C4::Context->dbh;
1219     my $sth = $dbh->prepare("UPDATE import_batches SET item_action = ? WHERE import_batch_id = ?");
1220     $sth->execute($new_item_action, $batch_id);
1221     $sth->finish();
1222
1223 }
1224
1225 =head2 GetImportBatchMatcher
1226
1227   my $matcher_id = GetImportBatchMatcher($batch_id);
1228
1229 =cut
1230
1231 sub GetImportBatchMatcher {
1232     my ($batch_id) = @_;
1233
1234     my $dbh = C4::Context->dbh;
1235     my $sth = $dbh->prepare("SELECT matcher_id FROM import_batches WHERE import_batch_id = ?");
1236     $sth->execute($batch_id);
1237     my ($matcher_id) = $sth->fetchrow_array();
1238     $sth->finish();
1239     return $matcher_id;
1240
1241 }
1242
1243
1244 =head2 SetImportBatchMatcher
1245
1246   SetImportBatchMatcher($batch_id, $new_matcher_id);
1247
1248 =cut
1249
1250 sub SetImportBatchMatcher {
1251     my ($batch_id, $new_matcher_id) = @_;
1252
1253     my $dbh = C4::Context->dbh;
1254     my $sth = $dbh->prepare("UPDATE import_batches SET matcher_id = ? WHERE import_batch_id = ?");
1255     $sth->execute($new_matcher_id, $batch_id);
1256     $sth->finish();
1257
1258 }
1259
1260 =head2 GetImportRecordOverlayStatus
1261
1262   my $overlay_status = GetImportRecordOverlayStatus($import_record_id);
1263
1264 =cut
1265
1266 sub GetImportRecordOverlayStatus {
1267     my ($import_record_id) = @_;
1268
1269     my $dbh = C4::Context->dbh;
1270     my $sth = $dbh->prepare("SELECT overlay_status FROM import_records WHERE import_record_id = ?");
1271     $sth->execute($import_record_id);
1272     my ($overlay_status) = $sth->fetchrow_array();
1273     $sth->finish();
1274     return $overlay_status;
1275
1276 }
1277
1278
1279 =head2 SetImportRecordOverlayStatus
1280
1281   SetImportRecordOverlayStatus($import_record_id, $new_overlay_status);
1282
1283 =cut
1284
1285 sub SetImportRecordOverlayStatus {
1286     my ($import_record_id, $new_overlay_status) = @_;
1287
1288     my $dbh = C4::Context->dbh;
1289     my $sth = $dbh->prepare("UPDATE import_records SET overlay_status = ? WHERE import_record_id = ?");
1290     $sth->execute($new_overlay_status, $import_record_id);
1291     $sth->finish();
1292
1293 }
1294
1295 =head2 GetImportRecordStatus
1296
1297   my $overlay_status = GetImportRecordStatus($import_record_id);
1298
1299 =cut
1300
1301 sub GetImportRecordStatus {
1302     my ($import_record_id) = @_;
1303
1304     my $dbh = C4::Context->dbh;
1305     my $sth = $dbh->prepare("SELECT status FROM import_records WHERE import_record_id = ?");
1306     $sth->execute($import_record_id);
1307     my ($overlay_status) = $sth->fetchrow_array();
1308     $sth->finish();
1309     return $overlay_status;
1310
1311 }
1312
1313
1314 =head2 SetImportRecordStatus
1315
1316   SetImportRecordStatus($import_record_id, $new_overlay_status);
1317
1318 =cut
1319
1320 sub SetImportRecordStatus {
1321     my ($import_record_id, $new_overlay_status) = @_;
1322
1323     my $dbh = C4::Context->dbh;
1324     my $sth = $dbh->prepare("UPDATE import_records SET status = ? WHERE import_record_id = ?");
1325     $sth->execute($new_overlay_status, $import_record_id);
1326     $sth->finish();
1327
1328 }
1329
1330 =head2 GetImportRecordMatches
1331
1332   my $results = GetImportRecordMatches($import_record_id, $best_only);
1333
1334 =cut
1335
1336 sub GetImportRecordMatches {
1337     my $import_record_id = shift;
1338     my $best_only = @_ ? shift : 0;
1339
1340     my $dbh = C4::Context->dbh;
1341     # FIXME currently biblio only
1342     my $sth = $dbh->prepare_cached("SELECT title, author, biblionumber,
1343                                     candidate_match_id, score, record_type
1344                                     FROM import_records
1345                                     JOIN import_record_matches USING (import_record_id)
1346                                     LEFT JOIN biblio ON (biblionumber = candidate_match_id)
1347                                     WHERE import_record_id = ?
1348                                     ORDER BY score DESC, biblionumber DESC");
1349     $sth->bind_param(1, $import_record_id);
1350     my $results = [];
1351     $sth->execute();
1352     while (my $row = $sth->fetchrow_hashref) {
1353         if ($row->{'record_type'} eq 'auth') {
1354             $row->{'authorized_heading'} = C4::AuthoritiesMarc::GetAuthorizedHeading( { authid => $row->{'candidate_match_id'} } );
1355         }
1356         next if ($row->{'record_type'} eq 'biblio' && not $row->{'biblionumber'});
1357         push @$results, $row;
1358         last if $best_only;
1359     }
1360     $sth->finish();
1361
1362     return $results;
1363     
1364 }
1365
1366
1367 =head2 SetImportRecordMatches
1368
1369   SetImportRecordMatches($import_record_id, @matches);
1370
1371 =cut
1372
1373 sub SetImportRecordMatches {
1374     my $import_record_id = shift;
1375     my @matches = @_;
1376
1377     my $dbh = C4::Context->dbh;
1378     my $delsth = $dbh->prepare("DELETE FROM import_record_matches WHERE import_record_id = ?");
1379     $delsth->execute($import_record_id);
1380     $delsth->finish();
1381
1382     my $sth = $dbh->prepare("INSERT INTO import_record_matches (import_record_id, candidate_match_id, score)
1383                                     VALUES (?, ?, ?)");
1384     foreach my $match (@matches) {
1385         $sth->execute($import_record_id, $match->{'record_id'}, $match->{'score'});
1386     }
1387 }
1388
1389
1390 # internal functions
1391
1392 sub _create_import_record {
1393     my ($batch_id, $record_sequence, $marc_record, $record_type, $encoding, $z3950random, $marc_type) = @_;
1394
1395     my $dbh = C4::Context->dbh;
1396     my $sth = $dbh->prepare("INSERT INTO import_records (import_batch_id, record_sequence, marc, marcxml, 
1397                                                          record_type, encoding, z3950random)
1398                                     VALUES (?, ?, ?, ?, ?, ?, ?)");
1399     $sth->execute($batch_id, $record_sequence, $marc_record->as_usmarc(), $marc_record->as_xml($marc_type),
1400                   $record_type, $encoding, $z3950random);
1401     my $import_record_id = $dbh->{'mysql_insertid'};
1402     $sth->finish();
1403     return $import_record_id;
1404 }
1405
1406 sub _update_import_record_marc {
1407     my ($import_record_id, $marc_record, $marc_type) = @_;
1408
1409     my $dbh = C4::Context->dbh;
1410     my $sth = $dbh->prepare("UPDATE import_records SET marc = ?, marcxml = ?
1411                              WHERE  import_record_id = ?");
1412     $sth->execute($marc_record->as_usmarc(), $marc_record->as_xml($marc_type), $import_record_id);
1413     $sth->finish();
1414 }
1415
1416 sub _add_auth_fields {
1417     my ($import_record_id, $marc_record) = @_;
1418
1419     my $controlnumber;
1420     if ($marc_record->field('001')) {
1421         $controlnumber = $marc_record->field('001')->data();
1422     }
1423     my $authorized_heading = C4::AuthoritiesMarc::GetAuthorizedHeading({ record => $marc_record });
1424     my $dbh = C4::Context->dbh;
1425     my $sth = $dbh->prepare("INSERT INTO import_auths (import_record_id, control_number, authorized_heading) VALUES (?, ?, ?)");
1426     $sth->execute($import_record_id, $controlnumber, $authorized_heading);
1427     $sth->finish();
1428 }
1429
1430 sub _add_biblio_fields {
1431     my ($import_record_id, $marc_record) = @_;
1432
1433     my ($title, $author, $isbn, $issn) = _parse_biblio_fields($marc_record);
1434     my $dbh = C4::Context->dbh;
1435     # FIXME no controlnumber, originalsource
1436     $isbn = C4::Koha::_isbn_cleanup($isbn); # FIXME C4::Koha::_isbn_cleanup should be made public
1437     my $sth = $dbh->prepare("INSERT INTO import_biblios (import_record_id, title, author, isbn, issn) VALUES (?, ?, ?, ?, ?)");
1438     $sth->execute($import_record_id, $title, $author, $isbn, $issn);
1439     $sth->finish();
1440                 
1441 }
1442
1443 sub _update_biblio_fields {
1444     my ($import_record_id, $marc_record) = @_;
1445
1446     my ($title, $author, $isbn, $issn) = _parse_biblio_fields($marc_record);
1447     my $dbh = C4::Context->dbh;
1448     # FIXME no controlnumber, originalsource
1449     # FIXME 2 - should regularize normalization of ISBN wherever it is done
1450     $isbn =~ s/\(.*$//;
1451     $isbn =~ tr/ -_//;
1452     $isbn = uc $isbn;
1453     my $sth = $dbh->prepare("UPDATE import_biblios SET title = ?, author = ?, isbn = ?, issn = ?
1454                              WHERE  import_record_id = ?");
1455     $sth->execute($title, $author, $isbn, $issn, $import_record_id);
1456     $sth->finish();
1457 }
1458
1459 sub _parse_biblio_fields {
1460     my ($marc_record) = @_;
1461
1462     my $dbh = C4::Context->dbh;
1463     my $bibliofields = TransformMarcToKoha($dbh, $marc_record, '');
1464     return ($bibliofields->{'title'}, $bibliofields->{'author'}, $bibliofields->{'isbn'}, $bibliofields->{'issn'});
1465
1466 }
1467
1468 sub _update_batch_record_counts {
1469     my ($batch_id) = @_;
1470
1471     my $dbh = C4::Context->dbh;
1472     my $sth = $dbh->prepare_cached("UPDATE import_batches SET
1473                                         num_records = (
1474                                             SELECT COUNT(*)
1475                                             FROM import_records
1476                                             WHERE import_batch_id = import_batches.import_batch_id),
1477                                         num_items = (
1478                                             SELECT COUNT(*)
1479                                             FROM import_records
1480                                             JOIN import_items USING (import_record_id)
1481                                             WHERE import_batch_id = import_batches.import_batch_id
1482                                             AND record_type = 'biblio')
1483                                     WHERE import_batch_id = ?");
1484     $sth->bind_param(1, $batch_id);
1485     $sth->execute();
1486     $sth->finish();
1487 }
1488
1489 sub _get_commit_action {
1490     my ($overlay_action, $nomatch_action, $item_action, $overlay_status, $import_record_id, $record_type) = @_;
1491     
1492     if ($record_type eq 'biblio') {
1493         my ($bib_result, $bib_match, $item_result);
1494
1495         if ($overlay_status ne 'no_match') {
1496             $bib_match = GetBestRecordMatch($import_record_id);
1497             if ($overlay_action eq 'replace') {
1498                 $bib_result  = defined($bib_match) ? 'replace' : 'create_new';
1499             } elsif ($overlay_action eq 'create_new') {
1500                 $bib_result  = 'create_new';
1501             } elsif ($overlay_action eq 'ignore') {
1502                 $bib_result  = 'ignore';
1503             }
1504          if($item_action eq 'always_add' or $item_action eq 'add_only_for_matches'){
1505                 $item_result = 'create_new';
1506        }
1507       elsif($item_action eq 'replace'){
1508           $item_result = 'replace';
1509           }
1510       else {
1511              $item_result = 'ignore';
1512            }
1513         } else {
1514             $bib_result = $nomatch_action;
1515             $item_result = ($item_action eq 'always_add' or $item_action eq 'add_only_for_new')     ? 'create_new' : 'ignore';
1516         }
1517         return ($bib_result, $item_result, $bib_match);
1518     } else { # must be auths
1519         my ($auth_result, $auth_match);
1520
1521         if ($overlay_status ne 'no_match') {
1522             $auth_match = GetBestRecordMatch($import_record_id);
1523             if ($overlay_action eq 'replace') {
1524                 $auth_result  = defined($auth_match) ? 'replace' : 'create_new';
1525             } elsif ($overlay_action eq 'create_new') {
1526                 $auth_result  = 'create_new';
1527             } elsif ($overlay_action eq 'ignore') {
1528                 $auth_result  = 'ignore';
1529             }
1530         } else {
1531             $auth_result = $nomatch_action;
1532         }
1533
1534         return ($auth_result, undef, $auth_match);
1535
1536     }
1537 }
1538
1539 sub _get_revert_action {
1540     my ($overlay_action, $overlay_status, $status) = @_;
1541
1542     my $bib_result;
1543
1544     if ($status eq 'ignored') {
1545         $bib_result = 'ignore';
1546     } else {
1547         if ($overlay_action eq 'create_new') {
1548             $bib_result = 'delete';
1549         } else {
1550             $bib_result = ($overlay_status eq 'match_applied') ? 'restore' : 'delete';
1551         }
1552     }
1553     return $bib_result;
1554 }
1555
1556 1;
1557 __END__
1558
1559 =head1 AUTHOR
1560
1561 Koha Development Team <http://koha-community.org/>
1562
1563 Galen Charlton <galen.charlton@liblime.com>
1564
1565 =cut