@@ -, +, @@ 1) Ensure that normal record merging still correctly functions. 2) Import a record that matches an existing record (exporting an existing record and reimporting it is an easy way to test this). 3) On the manage import batch page, there should now be a link to Merge. 4) Clicking this and going through the merge process should leave both records in place but update the import record. --- C4/ImportBatch.pm | 2 +- cataloguing/merge.pl | 275 +++++++++++++++------ koha-tmpl/intranet-tmpl/prog/css/merge-record.css | 61 +++++ .../prog/en/includes/authorities_js.inc | 2 +- .../prog/en/includes/merge-record.inc | 9 +- .../prog/en/modules/cataloguing/addbooks.tt | 2 +- .../prog/en/modules/cataloguing/merge.tt | 25 +- .../prog/en/modules/tools/manage-marc-import.tt | 5 +- .../prog/en/modules/virtualshelves/shelves.tt | 2 +- koha-tmpl/intranet-tmpl/prog/js/merge-record.js | 58 +++++ tools/batch_records_ajax.pl | 7 +- 11 files changed, 343 insertions(+), 105 deletions(-) create mode 100644 koha-tmpl/intranet-tmpl/prog/css/merge-record.css --- a/C4/ImportBatch.pm +++ a/C4/ImportBatch.pm @@ -1125,7 +1125,7 @@ sub GetImportRecordsRange { $order_by .= " $order_by_direction, authorized_heading" if $order_by eq 'title'; - my $query = "SELECT title, author, isbn, issn, authorized_heading, import_records.import_record_id, + my $query = "SELECT title, author, isbn, issn, authorized_heading, import_records.import_record_id, import_records.import_batch_id, record_sequence, status, overlay_status, matched_biblionumber, matched_authid, record_type FROM import_records --- a/cataloguing/merge.pl +++ a/cataloguing/merge.pl @@ -29,12 +29,12 @@ use C4::Serials; use C4::Koha; use C4::Reserves qw/MergeHolds/; use C4::Acquisition qw/ModOrder GetOrdersByBiblionumber/; - +use C4::ImportBatch; use Koha::BiblioFrameworks; use Koha::MetadataRecord; my $input = new CGI; -my @biblionumbers = $input->multi_param('biblionumber'); +my @record_paths = $input->multi_param('record'); my $merge = $input->param('merge'); my @errors; @@ -49,17 +49,41 @@ my ( $template, $loggedinuser, $cookie ) = get_template_and_user( } ); +my $sources = { + catalog => { + delete => \&_delete_catalog, + fetch => \&_fetch_catalog, + get_link => \&_get_link_catalog, + get_preview_link => \&_get_preview_link_catalog, + merge_other => \&_merge_other_catalog, + update => \&_update_catalog, + }, + batch => { + fetch => \&_fetch_import_batch, + get_link => \&_get_preview_link_import_batch, + get_preview_link => \&_get_preview_link_import_batch, + update => \&_update_import_batch, + }, +}; + +sub split_record_path { + my ($source, $num) = split /\//, shift, 2; + $source =~ s/-.*//; + + return ( $source, $num ); +} + #------------------------ # Merging #------------------------ if ($merge) { - my $dbh = C4::Context->dbh; - # Creating a new record from the html code - my $record = TransformHtmlToMarc( $input, 1 ); - my $ref_biblionumber = $input->param('ref_biblionumber'); - @biblionumbers = grep { $_ != $ref_biblionumber } @biblionumbers; + my $record = TransformHtmlToMarc( $input ); + my $ref_record_path = $input->param('ref_record'); + + my ( $ref_record_source, $ref_record_num ) = split_record_path $ref_record_path; + my @other_record_paths = map +[ split_record_path $_ ], grep { $_ ne $ref_record_path } @record_paths; # prepare report my @report_records; @@ -77,46 +101,46 @@ if ($merge) { } # Rewriting the leader - $record->leader(GetMarcBiblio($ref_biblionumber)->leader()); + my $orig_ref_record = $sources->{$ref_record_source}->{fetch}($ref_record_num); + $record->leader( $orig_ref_record->leader() ); my $frameworkcode = $input->param('frameworkcode'); my @notmoveditems; # Modifying the reference record - ModBiblio($record, $ref_biblionumber, $frameworkcode); - - # Moving items from the other record to the reference record - foreach my $biblionumber (@biblionumbers) { - my $itemnumbers = get_itemnumbers_of($biblionumber); - foreach my $itemnumber (@{ $itemnumbers->{$biblionumber} }) { - my $res = MoveItemFromBiblio($itemnumber, $biblionumber, $ref_biblionumber); - if (not defined $res) { - push @notmoveditems, $itemnumber; + $sources->{$ref_record_source}->{update}( $record, $ref_record_num, $frameworkcode ); + + # Are all the records from the same source? + my $all_records_same_source = 1; + + # Merging in information from other records + foreach my $record_path (@other_record_paths) { + my ( $record_source, $record_num ) = @$record_path; + + if ( $record_source eq $ref_record_source ) { + # We only want to merge in information from the other records if they're the same kind. + # + # We can't merge serials from an import bib to a catalog bib, for instance. + my @record_errors = $sources->{$record_source}->{merge_other}( $record_num, $ref_record_num ); + + if ( @record_errors ) { + push @errors, @record_errors; + } else { + push @errors, $sources->{$record_source}->{delete}( $record_num ); + } + } else { + $all_records_same_source = 0; } } - } - # If some items could not be moved : - if (scalar(@notmoveditems) > 0) { - my $itemlist = join(' ',@notmoveditems); - push @errors, { code => "CANNOT_MOVE", value => $itemlist }; - } - - my $sth_subscription = $dbh->prepare(" - UPDATE subscription SET biblionumber = ? WHERE biblionumber = ? - "); - my $sth_subscriptionhistory = $dbh->prepare(" - UPDATE subscriptionhistory SET biblionumber = ? WHERE biblionumber = ? - "); - my $sth_serial = $dbh->prepare(" - UPDATE serial SET biblionumber = ? WHERE biblionumber = ? - "); my $report_header = {}; - foreach my $biblionumber ($ref_biblionumber, @biblionumbers) { + foreach my $record_path ( [ $ref_record_source, $ref_record_num ], @other_record_paths ) { + my ( $record_source, $record_num ) = @$record_path; + # build report - my $marcrecord = GetMarcBiblio($biblionumber); + my $marcrecord = $sources->{$record_source}->{fetch}($record_num); my %report_record = ( - biblionumber => $biblionumber, + record_num => $record_num, fields => {}, ); foreach my $field (@report_fields) { @@ -145,53 +169,29 @@ if ($merge) { push @report_records, \%report_record; } - foreach my $biblionumber (@biblionumbers) { - # Moving subscriptions from the other record to the reference record - my $subcount = CountSubscriptionFromBiblionumber($biblionumber); - if ($subcount > 0) { - $sth_subscription->execute($ref_biblionumber, $biblionumber); - $sth_subscriptionhistory->execute($ref_biblionumber, $biblionumber); - } - - # Moving serials - $sth_serial->execute($ref_biblionumber, $biblionumber); - - # Moving orders (orders linked to items of frombiblio have already been moved by MoveItemFromBiblio) - my @allorders = GetOrdersByBiblionumber($biblionumber); - my @tobiblioitem = GetBiblioItemByBiblioNumber ($ref_biblionumber); - my $tobiblioitem_biblioitemnumber = $tobiblioitem [0]-> {biblioitemnumber }; - foreach my $myorder (@allorders) { - $myorder->{'biblionumber'} = $ref_biblionumber; - ModOrder ($myorder); - # TODO : add error control (in ModOrder?) - } - - # Deleting the other records - if (scalar(@errors) == 0) { - # Move holds - MergeHolds($dbh, $ref_biblionumber, $biblionumber); - my $error = DelBiblio($biblionumber); - push @errors, $error if ($error); - } -} - # Parameters $template->param( + all_records_same_source => $all_records_same_source, result => 1, report_records => \@report_records, report_header => $report_header, - ref_biblionumber => scalar $input->param('ref_biblionumber') + ref_record_path => scalar $input->param('ref_record'), + ref_record_id => "$ref_record_source-$ref_record_num", + ref_record_link => $sources->{$ref_record_source}->{get_link}($ref_record_num), ); #------------------------- # Show records to merge #------------------------- } else { - my $ref_biblionumber = $input->param('ref_biblionumber'); + my $ref_record_path = $input->param('ref_record'); + + if ($ref_record_path) { + my ( $ref_record_source, $ref_record_num ) = split_record_path $ref_record_path; - if ($ref_biblionumber) { my $framework = $input->param('frameworkcode'); - $framework //= GetFrameworkCode($ref_biblionumber); + $framework //= GetFrameworkCode($ref_record_num) if $ref_record_source eq 'catalog'; + $framework //= ''; # Getting MARC Structure my $tagslib = GetMarcStructure(1, $framework); @@ -200,19 +200,24 @@ if ($merge) { # Creating a loop for display my @records; - foreach my $biblionumber (@biblionumbers) { - my $marcrecord = GetMarcBiblio($biblionumber); - my $frameworkcode = GetFrameworkCode($biblionumber); + foreach my $record_path ( $input->param('record') ) { + my ( $record_source, $record_num ) = split_record_path $record_path; + my $marcrecord = $sources->{$record_source}->{fetch}($record_num); + # FIXME: We assume the default framework for import records. + my $frameworkcode = $record_source eq 'catalog' ? GetFrameworkCode($record_num) : ''; my $recordObj = new Koha::MetadataRecord({'record' => $marcrecord, schema => $marcflavour}); my $record = { - recordid => $biblionumber, + record_path => $record_path, + recordid => "$record_source-$record_num", + record_source => $record_source, + record_num => $record_num, record => $marcrecord, frameworkcode => $frameworkcode, display => $recordObj->createMergeHash($tagslib), }; - if ($ref_biblionumber and $ref_biblionumber == $biblionumber) { + if ($ref_record_path and $ref_record_path eq $record_path) { $record->{reference} = 1; - $template->param(ref_record => $record); + $template->param(ref_record_path => $record); unshift @records, $record; } else { push @records, $record; @@ -223,7 +228,8 @@ if ($merge) { # Parameters $template->param( - ref_biblionumber => $ref_biblionumber, + ref_record_path => $ref_record_path, + ref_recordid => "$ref_record_source-$ref_record_num", records => \@records, ref_record => $records[0], framework => $framework, @@ -232,11 +238,19 @@ if ($merge) { ); } else { my @records; - foreach my $biblionumber (@biblionumbers) { - my $frameworkcode = GetFrameworkCode($biblionumber); + foreach my $record_path ( $input->multi_param('record') ) { + my ( $record_source, $record_num ) = split_record_path $record_path; + + my $marcrecord = $sources->{$record_source}->{fetch}($record_num); + # FIXME: We assume the default framework for import records. + my $frameworkcode = $record_source eq 'catalog' ? GetFrameworkCode($record_num) : ''; my $record = { - biblionumber => $biblionumber, - data => GetBiblioData($biblionumber), + record_path => $record_path, + recordid => "$record_path-$record_num", + record_source => $record_source, + record_num => $record_num, + preview_link => $sources->{$record_source}->{get_preview_link}($record_num), + data => TransformMarcToKoha( $marcrecord, $frameworkcode ), frameworkcode => $frameworkcode, }; push @records, $record; @@ -258,4 +272,101 @@ if (@errors) { } output_html_with_http_headers $input, $cookie, $template->output; -exit; + +sub _delete_catalog { + my ( $biblionumber ) = @_; + return DelBiblio($biblionumber); +} + +sub _fetch_catalog { + my ( $biblionumber ) = @_; + + return GetMarcBiblio( $biblionumber ); +} + +sub _get_link_catalog { + my ( $biblionumber ) = @_; + + return "/cgi-bin/koha/catalogue/MARCdetail.pl?biblionumber=$biblionumber"; +} + +sub _get_preview_link_catalog { + my ( $biblionumber ) = @_; + + return "/cgi-bin/koha/catalogue/showmarc.pl?id=$biblionumber"; +} + +sub _merge_other_catalog { + my ( $biblionumber, $ref_biblionumber ) = @_; + + my $dbh = C4::Context->dbh; + my @errors; + + my @notmoveditems; + my $itemnumbers = get_itemnumbers_of($biblionumber); + foreach my $itemnumber (@{ $itemnumbers->{$biblionumber} }) { + if ( !MoveItemFromBiblio( $itemnumber, $biblionumber, $ref_biblionumber ) ) { + push @notmoveditems, $itemnumber; + } + } + + # If some items could not be moved : + if (scalar(@notmoveditems) > 0) { + my $itemlist = join(' ',@notmoveditems); + push @errors, { code => "CANNOT_MOVE", value => $itemlist }; + } + + my $sth_subscription = $dbh->prepare(" + UPDATE subscription SET biblionumber = ? WHERE biblionumber = ? + "); + my $sth_subscriptionhistory = $dbh->prepare(" + UPDATE subscriptionhistory SET biblionumber = ? WHERE biblionumber = ? + "); + my $sth_serial = $dbh->prepare(" + UPDATE serial SET biblionumber = ? WHERE biblionumber = ? + "); + + # Moving subscriptions from the other record to the reference record + my $subcount = CountSubscriptionFromBiblionumber($biblionumber); + if ($subcount > 0) { + $sth_subscription->execute($ref_biblionumber, $biblionumber); + $sth_subscriptionhistory->execute($ref_biblionumber, $biblionumber); + } + + # Moving serials + $sth_serial->execute($ref_biblionumber, $biblionumber); + + # Moving orders (orders linked to items of frombiblio have already been moved by MoveItemFromBiblio) + foreach my $myorder( GetOrdersByBiblionumber($biblionumber) ) { + $myorder->{'biblionumber'} = $ref_biblionumber; + ModOrder ($myorder); + # TODO : add error control (in ModOrder?) + } + + if (scalar(@errors) == 0) { + MergeHolds($dbh, $ref_biblionumber, $biblionumber); + } + + return @errors; +} + +sub _update_catalog { + my ( $record, $biblionumber, $frameworkcode ) = @_; + ModBiblio( $record, $biblionumber, $frameworkcode ); +} + +sub _fetch_import_batch { + my ( $import_recordid ) = @_; + return C4::ImportBatch::GetRecordFromImportBiblio( $import_recordid ); +} + +sub _get_preview_link_import_batch { + my ( $import_recordid ) = @_; + + return "/cgi-bin/koha/catalogue/showmarc.pl?importid=$import_recordid"; +} + +sub _update_import_batch { + my ( $record, $import_recordid ) = @_; + ModBiblioInBatch( $import_recordid, $record ); +} --- a/koha-tmpl/intranet-tmpl/prog/css/merge-record.css +++ a/koha-tmpl/intranet-tmpl/prog/css/merge-record.css @@ -0,0 +1,61 @@ +.merge-record-fixed-contents { + display: inline-block; +} + +.merge-record-fixed-contents, .merge-record-subfield { + padding: 2px; +} + +.merge-record-unique-0, #tabs .merge-record-unique-0 { + background-color: #005AC8; + color: white; +} + +.merge-record-unique-1, #tabs .merge-record-unique-1 { + background-color: #AA0A3C; + color: white; +} + +.merge-record-unique-2, #tabs .merge-record-unique-2 { + background-color: #F0F032; +} + +.merge-record-unique-3, #tabs .merge-record-unique-3 { + background-color: #00A0FA; + color: white; +} + +.merge-record-unique-4, #tabs .merge-record-unique-4 { + background-color: #FA78FA; +} + +.merge-record-unique-5, #tabs .merge-record-unique-5 { + background-color: #14D2DC; +} + +.merge-record-unique-6, #tabs .merge-record-unique-6 { + background-color: #8214A0; + color: white; +} + +.merge-record-unique-7, #tabs .merge-record-unique-7 { + background-color: #FA7850; +} + +.merge-record-unique-8, #tabs .merge-record-unique-8 { + background-color: #0AB45A; + color: white; +} + +.merge-record-unique-9, #tabs .merge-record-unique-9 { + background-color: #006E82; + color: white; +} + +.merge-record-unique-10, #tabs .merge-record-unique-10 { + background-color: #A0FA82; +} + +.merge-record-unique-11, #tabs .merge-record-unique-11 { + background-color: #F9E5BD; +} --- a/koha-tmpl/intranet-tmpl/prog/en/includes/authorities_js.inc +++ a/koha-tmpl/intranet-tmpl/prog/en/includes/authorities_js.inc @@ -9,7 +9,7 @@ function mergeAuth(authid, summary) { if (typeof alreadySelected.mergereference !== 'undefined') { refstring = "&mergereference=" + alreadySelected.mergereference; } - window.location.href = "/cgi-bin/koha/authorities/merge.pl?authid=" + authid + "&authid=" + alreadySelected.authid + refstring; + window.location.href = "/cgi-bin/koha/authorities/merge.pl?record=catalog/" + authid + "&record=catalog/" + alreadySelected.authid + refstring; } else { $.cookie('auth_to_merge', JSON.stringify({ 'authid': authid, 'summary': summary }), { 'path' : '/' }); showMergingInProgress(); --- a/koha-tmpl/intranet-tmpl/prog/en/includes/merge-record.inc +++ a/koha-tmpl/intranet-tmpl/prog/en/includes/merge-record.inc @@ -4,7 +4,7 @@