3 # Copyright ByWater Solutions 2014
5 # This file is part of Koha.
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.
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.
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>.
22 use List::MoreUtils qw( any );
24 use URI::Escape qw( uri_escape_utf8 );
27 use C4::Koha qw( GetNormalizedISBN GetNormalizedUPC GetNormalizedOCLCNumber );
28 use C4::Biblio qw( DelBiblio );
29 use C4::Serials qw( CountSubscriptionFromBiblionumber );
30 use C4::Reserves qw( MergeHolds );
31 use C4::Acquisition qw( ModOrder GetOrdersByBiblionumber );
34 use Koha::DateUtils qw( dt_from_string );
37 use base qw(Koha::Object);
39 use Koha::Acquisition::Orders;
40 use Koha::ArticleRequests;
41 use Koha::Biblio::Metadatas;
42 use Koha::Biblio::Metadata::Extractor;
43 use Koha::Biblio::ItemGroups;
44 use Koha::Biblioitems;
45 use Koha::Cache::Memory::Lite;
48 use Koha::CirculationRules;
50 use Koha::Illrequests;
51 use Koha::Item::Transfer::Limits;
54 use Koha::Old::Checkouts;
57 use Koha::RecordProcessor;
58 use Koha::Suggestions;
59 use Koha::Subscriptions;
60 use Koha::SearchEngine;
61 use Koha::SearchEngine::Search;
62 use Koha::SearchEngine::QueryBuilder;
67 Koha::Biblio - Koha Biblio Object class
77 Overloaded I<store> method to set default values
84 $self->datecreated( dt_from_string ) unless $self->datecreated;
86 return $self->SUPER::store;
91 my $metadata = $biblio->metadata();
93 Returns a Koha::Biblio::Metadata object
100 my $metadata = $self->_result->metadata;
101 return Koha::Biblio::Metadata->_new_from_dbic($metadata);
106 my $record = $biblio->record();
108 Returns a Marc::Record object
115 return $self->metadata->record;
120 my $schema = $biblio->record_schema();
122 Returns the record schema (MARC21, USMARC or UNIMARC).
129 return $self->metadata->schema // C4::Context->preference("marcflavour");
134 my $orders = $biblio->orders();
136 Returns a Koha::Acquisition::Orders object
143 my $orders = $self->_result->orders;
144 return Koha::Acquisition::Orders->_new_from_dbic($orders);
149 my $active_orders = $biblio->active_orders();
151 Returns the active acquisition orders related to this biblio.
152 An order is considered active when it is not cancelled (i.e. when datecancellation
160 return $self->orders->search({ datecancellationprinted => undef });
165 my $tickets = $biblio->tickets();
167 Returns all tickets linked to the biblio
173 my $rs = $self->_result->tickets;
174 return Koha::Tickets->_new_from_dbic( $rs );
179 my $ill_requests = $biblio->ill_requests();
181 Returns a Koha::Illrequests object
188 my $ill_requests = $self->_result->ill_requests;
189 return Koha::Illrequests->_new_from_dbic($ill_requests);
194 my $item_groups = $biblio->item_groups();
196 Returns a Koha::Biblio::ItemGroups object
203 my $item_groups = $self->_result->item_groups;
204 return Koha::Biblio::ItemGroups->_new_from_dbic($item_groups);
207 =head3 can_article_request
209 my $bool = $biblio->can_article_request( $borrower );
211 Returns true if article requests can be made for this record
213 $borrower must be a Koha::Patron object
217 sub can_article_request {
218 my ( $self, $borrower ) = @_;
220 my $rule = $self->article_request_type($borrower);
221 return q{} if $rule eq 'item_only' && !$self->items()->count();
222 return 1 if $rule && $rule ne 'no';
232 $biblio->check_booking( { start_date => $datetime, end_date => $datetime, [ booking_id => $booking_id ] } );
234 Returns a boolean denoting whether the passed booking can be made without clashing.
236 Optionally, you may pass a booking id to exclude from the checks; This is helpful when you are updating an existing booking.
241 my ( $self, $params ) = @_;
243 my $start_date = dt_from_string( $params->{start_date} );
244 my $end_date = dt_from_string( $params->{end_date} );
245 my $booking_id = $params->{booking_id};
247 my $bookable_items = $self->bookable_items;
248 my $total_bookable = $bookable_items->count;
250 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
251 my $existing_bookings = $self->bookings(
255 $dtf->format_datetime($start_date),
256 $dtf->format_datetime($end_date)
261 $dtf->format_datetime($start_date),
262 $dtf->format_datetime($end_date)
266 start_date => { '<' => $dtf->format_datetime($start_date) },
267 end_date => { '>' => $dtf->format_datetime($end_date) }
274 ? $existing_bookings->search( { booking_id => { '!=' => $booking_id } } )->count
275 : $existing_bookings->count;
277 my $checkouts = $self->current_checkouts->search( { date_due => { '>=' => $dtf->format_datetime($start_date) } } );
278 $booked_count += $checkouts->count;
280 return ( ( $total_bookable - $booked_count ) > 0 ) ? 1 : 0;
283 =head3 can_be_transferred
285 $biblio->can_be_transferred({ to => $to_library, from => $from_library })
287 Checks if at least one item of a biblio can be transferred to given library.
289 This feature is controlled by two system preferences:
290 UseBranchTransferLimits to enable / disable the feature
291 BranchTransferLimitsType to use either an itemnumber or ccode as an identifier
292 for setting the limitations
294 Performance-wise, it is recommended to use this method for a biblio instead of
295 iterating each item of a biblio with Koha::Item->can_be_transferred().
297 Takes HASHref that can have the following parameters:
298 MANDATORY PARAMETERS:
301 $from : Koha::Library # if given, only items from that
302 # holdingbranch are considered
304 Returns 1 if at least one of the item of a biblio can be transferred
305 to $to_library, otherwise 0.
309 sub can_be_transferred {
310 my ($self, $params) = @_;
312 my $to = $params->{to};
313 my $from = $params->{from};
315 return 1 unless C4::Context->preference('UseBranchTransferLimits');
316 my $limittype = C4::Context->preference('BranchTransferLimitsType');
319 foreach my $item_of_bib ($self->items->as_list) {
320 next unless $item_of_bib->holdingbranch;
321 next if $from && $from->branchcode ne $item_of_bib->holdingbranch;
322 return 1 if $item_of_bib->holdingbranch eq $to->branchcode;
323 my $code = $limittype eq 'itemtype'
324 ? $item_of_bib->effective_itemtype
325 : $item_of_bib->ccode;
326 return 1 unless $code;
327 $items->{$code}->{$item_of_bib->holdingbranch} = 1;
330 # At this point we will have a HASHref containing each itemtype/ccode that
331 # this biblio has, inside which are all of the holdingbranches where those
332 # items are located at. Then, we will query Koha::Item::Transfer::Limits to
333 # find out whether a transfer limits for such $limittype from any of the
334 # listed holdingbranches to the given $to library exist. If at least one
335 # holdingbranch for that $limittype does not have a transfer limit to given
336 # $to library, then we know that the transfer is possible.
337 foreach my $code (keys %{$items}) {
338 my @holdingbranches = keys %{$items->{$code}};
339 return 1 if Koha::Item::Transfer::Limits->search({
340 toBranch => $to->branchcode,
341 fromBranch => { 'in' => \@holdingbranches },
344 group_by => [qw/fromBranch/]
345 })->count == scalar(@holdingbranches) ? 0 : 1;
352 =head3 pickup_locations
354 my $pickup_locations = $biblio->pickup_locations({ patron => $patron });
356 Returns a Koha::Libraries set of possible pickup locations for this biblio's items,
357 according to patron's home library and if item can be transferred to each pickup location.
359 Throws a I<Koha::Exceptions::MissingParameter> exception if the B<mandatory> parameter I<patron>
364 sub pickup_locations {
365 my ( $self, $params ) = @_;
367 Koha::Exceptions::MissingParameter->throw( parameter => 'patron' )
368 unless exists $params->{patron};
370 my $patron = $params->{patron};
372 my $memory_cache = Koha::Cache::Memory::Lite->get_instance();
373 my @pickup_locations;
374 foreach my $item ( $self->items->as_list ) {
375 my $cache_key = sprintf "Pickup_locations:%s:%s:%s:%s:%s",
376 $item->itype,$item->homebranch,$item->holdingbranch,$item->ccode || "",$patron->branchcode||"" ;
377 my $item_pickup_locations = $memory_cache->get_from_cache( $cache_key );
378 unless( $item_pickup_locations ){
379 @{ $item_pickup_locations } = $item->pickup_locations( { patron => $patron } )->_resultset->get_column('branchcode')->all;
380 $memory_cache->set_in_cache( $cache_key, $item_pickup_locations );
382 push @pickup_locations, @{ $item_pickup_locations }
385 return Koha::Libraries->search(
386 { branchcode => { '-in' => \@pickup_locations } }, { order_by => ['branchname'] } );
389 =head3 hidden_in_opac
391 my $bool = $biblio->hidden_in_opac({ [ rules => $rules ] })
393 Returns true if the biblio matches the hidding criteria defined in $rules.
394 Returns false otherwise. It involves the I<OpacHiddenItems> and
395 I<OpacHiddenItemsHidesRecord> system preferences.
397 Takes HASHref that can have the following parameters:
399 $rules : { <field> => [ value_1, ... ], ... }
401 Note: $rules inherits its structure from the parsed YAML from reading
402 the I<OpacHiddenItems> system preference.
407 my ( $self, $params ) = @_;
409 my $rules = $params->{rules} // {};
411 my @items = $self->items->as_list;
413 return 0 unless @items; # Do not hide if there is no item
415 # Ok, there are items, don't even try the rules unless OpacHiddenItemsHidesRecord
416 return 0 unless C4::Context->preference('OpacHiddenItemsHidesRecord');
418 return !(any { !$_->hidden_in_opac({ rules => $rules }) } @items);
421 =head3 article_request_type
423 my $type = $biblio->article_request_type( $borrower );
425 Returns the article request type based on items, or on the record
426 itself if there are no items.
428 $borrower must be a Koha::Patron object
432 sub article_request_type {
433 my ( $self, $borrower ) = @_;
435 return q{} unless $borrower;
437 my $rule = $self->article_request_type_for_items( $borrower );
438 return $rule if $rule;
440 # If the record has no items that are requestable, go by the record itemtype
441 $rule = $self->article_request_type_for_bib($borrower);
442 return $rule if $rule;
447 =head3 article_request_type_for_bib
449 my $type = $biblio->article_request_type_for_bib
451 Returns the article request type 'yes', 'no', 'item_only', 'bib_only', for the given record
455 sub article_request_type_for_bib {
456 my ( $self, $borrower ) = @_;
458 return q{} unless $borrower;
460 my $borrowertype = $borrower->categorycode;
461 my $itemtype = $self->itemtype();
463 my $rule = Koha::CirculationRules->get_effective_rule(
465 rule_name => 'article_requests',
466 categorycode => $borrowertype,
467 itemtype => $itemtype,
471 return q{} unless $rule;
472 return $rule->rule_value || q{}
475 =head3 article_request_type_for_items
477 my $type = $biblio->article_request_type_for_items
479 Returns the article request type 'yes', 'no', 'item_only', 'bib_only', for the given record's items
481 If there is a conflict where some items are 'bib_only' and some are 'item_only', 'bib_only' will be returned.
485 sub article_request_type_for_items {
486 my ( $self, $borrower ) = @_;
489 foreach my $item ( $self->items()->as_list() ) {
490 my $rule = $item->article_request_type($borrower);
491 return $rule if $rule eq 'bib_only'; # we don't need to go any further
495 return 'item_only' if $counts->{item_only};
496 return 'yes' if $counts->{yes};
497 return 'no' if $counts->{no};
501 =head3 article_requests
503 my $article_requests = $biblio->article_requests
505 Returns the article requests associated with this biblio
509 sub article_requests {
512 return Koha::ArticleRequests->_new_from_dbic( scalar $self->_result->article_requests );
515 =head3 current_checkouts
517 my $current_checkouts = $biblio->current_checkouts
519 Returns the current checkouts associated with this biblio
523 sub current_checkouts {
526 return Koha::Checkouts->search( { "item.biblionumber" => $self->id },
527 { join => 'item' } );
532 my $old_checkouts = $biblio->old_checkouts
534 Returns the past checkouts associated with this biblio
541 return Koha::Old::Checkouts->search( { "item.biblionumber" => $self->id },
542 { join => 'item' } );
547 my $items = $biblio->items({ [ host_items => 1 ] });
549 The optional param host_items allows you to include 'analytical' items.
551 Returns the related Koha::Items object for this biblio
556 my ($self,$params) = @_;
558 my $items_rs = $self->_result->items;
560 return Koha::Items->_new_from_dbic( $items_rs ) unless $params->{host_items};
562 my @itemnumbers = $items_rs->get_column('itemnumber')->all;
563 my $host_itemnumbers = $self->_host_itemnumbers();
564 push @itemnumbers, @{ $host_itemnumbers };
565 return Koha::Items->search({ "me.itemnumber" => { -in => \@itemnumbers } });
568 =head3 bookable_items
570 my $bookable_items = $biblio->bookable_items;
572 Returns the related Koha::Items resultset filtered to those items that can be booked.
578 return $self->items->filter_by_bookable;
583 my $host_items = $biblio->host_items();
585 Return the host items (easy analytical record)
592 return Koha::Items->new->empty
593 unless C4::Context->preference('EasyAnalyticalRecords');
595 my $host_itemnumbers = $self->_host_itemnumbers;
597 return Koha::Items->search( { itemnumber => { -in => $host_itemnumbers } } );
600 =head3 _host_itemnumbers
602 my $host_itemnumber = $biblio->_host_itemnumbers();
604 Return the itemnumbers for analytical items on this record
608 sub _host_itemnumbers {
611 my $marcflavour = C4::Context->preference("marcflavour");
612 my $analyticfield = '773';
613 if ( $marcflavour eq 'UNIMARC' ) {
614 $analyticfield = '461';
616 my $marc_record = $self->metadata->record;
618 foreach my $field ( $marc_record->field($analyticfield) ) {
619 push @itemnumbers, $field->subfield('9');
621 return \@itemnumbers;
627 my $itemtype = $biblio->itemtype();
629 Returns the itemtype for this record.
636 return $self->biblioitem()->itemtype();
641 my $holds = $biblio->holds();
643 return the current holds placed on this record
648 my ( $self, $params, $attributes ) = @_;
649 $attributes->{order_by} = 'priority' unless exists $attributes->{order_by};
650 my $hold_rs = $self->_result->reserves->search( $params, $attributes );
651 return Koha::Holds->_new_from_dbic($hold_rs);
656 my $holds = $biblio->current_holds
658 Return the holds placed on this bibliographic record.
659 It does not include future holds.
665 my $dtf = Koha::Database->new->schema->storage->datetime_parser;
667 { reservedate => { '<=' => $dtf->format_date(dt_from_string) } } );
672 my $field = $self->biblioitem
674 Returns the related Koha::Biblioitem object for this Biblio object
680 return Koha::Biblioitems->find( { biblionumber => $self->biblionumber } );
685 my $bookings = $item->bookings();
687 Returns the bookings attached to this biblio.
692 my ( $self, $params ) = @_;
693 my $bookings_rs = $self->_result->bookings->search($params);
694 return Koha::Bookings->_new_from_dbic($bookings_rs);
699 my $suggestions = $self->suggestions
701 Returns the related Koha::Suggestions object for this Biblio object
708 my $suggestions_rs = $self->_result->suggestions;
709 return Koha::Suggestions->_new_from_dbic( $suggestions_rs );
712 =head3 get_marc_components
714 my $components = $self->get_marc_components();
716 Returns an array of search results data, which are component parts of
717 this object (MARC21 773 points to this)
721 sub get_marc_components {
722 my ($self, $max_results) = @_;
724 return [] if (C4::Context->preference('marcflavour') ne 'MARC21');
726 my ( $searchstr, $sort ) = $self->get_components_query;
729 if (defined($searchstr)) {
730 my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
731 my ( $error, $results, $facets );
733 ( $error, $results, $facets ) = $searcher->search_compat( $searchstr, undef, [$sort], ['biblioserver'], $max_results, 0, undef, undef, 'ccl', 0 );
738 warn "Warning from search_compat: '$error'";
742 message => 'component_search',
747 $components = $results->{biblioserver}->{RECORDS} if defined($results) && $results->{biblioserver}->{hits};
750 return $components // [];
753 =head2 get_components_query
755 Returns a query which can be used to search for all component parts of MARC21 biblios
759 sub get_components_query {
762 my $builder = Koha::SearchEngine::QueryBuilder->new(
763 { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
764 my $marc = $self->metadata->record;
765 my $component_sort_field = C4::Context->preference('ComponentSortField') // "title";
766 my $component_sort_order = C4::Context->preference('ComponentSortOrder') // "asc";
767 my $sort = $component_sort_field . "_" . $component_sort_order;
770 if ( C4::Context->preference('UseControlNumber') ) {
771 my $pf001 = $marc->field('001') || undef;
773 if ( defined($pf001) ) {
775 my $pf003 = $marc->field('003') || undef;
777 if ( !defined($pf003) ) {
778 # search for 773$w='Host001'
779 $searchstr .= "rcn:\"" . $pf001->data()."\"";
783 # search for (773$w='Host001' and 003='Host003') or 773$w='(Host003)Host001'
784 $searchstr .= "(rcn:\"" . $pf001->data() . "\" AND cni:\"" . $pf003->data() . "\")";
785 $searchstr .= " OR rcn:\"" . $pf003->data() . " " . $pf001->data() . "\"";
789 # limit to monograph and serial component part records
790 $searchstr .= " AND (bib-level:a OR bib-level:b)";
795 my $cleaned_title = $marc->subfield('245', "a");
796 $cleaned_title =~ tr|/||;
797 $cleaned_title = $builder->clean_search_term($cleaned_title);
798 $searchstr = qq#Host-item:("$cleaned_title")#;
800 my ($error, $query ,$query_str) = $builder->build_query_compat( undef, [$searchstr], undef, undef, [$sort], 0 );
806 return ($query, $query_str, $sort);
809 =head3 get_marc_volumes
811 my $volumes = $self->get_marc_volumes();
813 Returns an array of MARCXML data, which are volumes parts of
814 this object (MARC21 773$w or 8xx$w point to this)
818 sub get_marc_volumes {
819 my ( $self, $max_results ) = @_;
821 return $self->{_volumes} if defined( $self->{_volumes} );
823 my $searchstr = $self->get_volumes_query;
825 if ( defined($searchstr) ) {
826 my $searcher = Koha::SearchEngine::Search->new( { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
827 my ( $errors, $results, $total_hits ) = $searcher->simple_search_compat( $searchstr, 0, $max_results );
829 ( defined($results) && scalar(@$results) ) ? $results : [];
831 $self->{_volumes} = [];
834 return $self->{_volumes};
837 =head2 get_volumes_query
839 Returns a query which can be used to search for all component parts of MARC21 biblios
843 sub get_volumes_query {
846 # MARC21 Only for now
847 return if ( C4::Context->preference('marcflavour') ne 'MARC21' );
849 my $marc = $self->metadata->record;
851 # Only build volumes query if we're in a 'Set' record
852 # or we have a monographic series.
853 # For monographic series the check on LDR 7 in (b or i or s) is omitted
854 my $leader19 = substr( $marc->leader, 19, 1 );
855 my $pf008 = $marc->field('008') || '';
856 my $mseries = ( $pf008 && substr( $pf008->data(), 21, 1 ) eq 'm' ) ? 1 : 0;
857 return unless ( $leader19 eq 'a' || $mseries );
859 my $builder = Koha::SearchEngine::QueryBuilder->new( { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
862 if ( C4::Context->preference('UseControlNumber') ) {
863 my $pf001 = $marc->field('001') || undef;
865 if ( defined($pf001) ) {
867 my $pf003 = $marc->field('003') || undef;
869 if ( !defined($pf003) ) {
871 # search for linking_field$w='Host001'
872 $searchstr .= "rcn:" . $pf001->data();
876 # search for (linking_field$w='Host001' and 003='Host003') or linking_field$w='(Host003)Host001'
877 $searchstr .= "(rcn:" . $pf001->data() . " AND cni:" . $pf003->data() . ")";
878 $searchstr .= " OR rcn:\"" . $pf003->data() . " " . $pf001->data() . "\"";
882 # exclude monograph and serial component part records
883 $searchstr .= " NOT (bib-level:a OR bib-level:b)";
887 my $cleaned_title = $marc->subfield( '245', "a" );
888 $cleaned_title =~ tr|/||;
889 $cleaned_title = $builder->clean_search_term($cleaned_title);
890 $searchstr = qq#(title-series,phr:("$cleaned_title") OR Host-item,phr:("$cleaned_title")#;
891 $searchstr .= " NOT (bib-level:a OR bib-level:b))";
899 my $subscriptions = $self->subscriptions
901 Returns the related Koha::Subscriptions object for this Biblio object
907 my $rs = $self->_result->subscriptions;
908 return Koha::Subscriptions->_new_from_dbic($rs);
913 my $serials = $self->serials
915 Returns the related Koha::Serials object for this Biblio object
921 my $rs = $self->_result->serials;
922 return Koha::Serials->_new_from_dbic($rs);
925 =head3 subscription_histories
927 my $subscription_histories = $self->subscription_histories
929 Returns the related Koha::Subscription::Histories object for this Biblio object
933 sub subscription_histories {
935 my $rs = $self->_result->subscriptionhistories;
936 return Koha::Subscription::Histories->_new_from_dbic($rs);
939 =head3 has_items_waiting_or_intransit
941 my $itemsWaitingOrInTransit = $biblio->has_items_waiting_or_intransit
943 Tells if this bibliographic record has items waiting or in transit.
947 sub has_items_waiting_or_intransit {
950 if ( Koha::Holds->search({ biblionumber => $self->id,
951 found => ['W', 'T'] })->count ) {
955 foreach my $item ( $self->items->as_list ) {
956 return 1 if $item->get_transfer;
964 my $coins = $biblio->get_coins;
966 Returns the COinS (a span) which can be included in a biblio record
973 my $record = $self->metadata->record;
975 my $pos7 = substr $record->leader(), 7, 1;
976 my $pos6 = substr $record->leader(), 6, 1;
979 my ( $aulast, $aufirst ) = ( '', '' );
990 # For the purposes of generating COinS metadata, LDR/06-07 can be
991 # considered the same for UNIMARC and MARC21
1000 'i' => 'audioRecording',
1001 'j' => 'audioRecording',
1004 'm' => 'computerProgram',
1009 'a' => 'journalArticle',
1013 $genre = $fmts6->{$pos6} ? $fmts6->{$pos6} : 'book';
1015 if ( $genre eq 'book' ) {
1016 $genre = $fmts7->{$pos7} if $fmts7->{$pos7};
1019 ##### We must transform mtx to a valable mtx and document type ####
1020 if ( $genre eq 'book' ) {
1023 } elsif ( $genre eq 'journal' ) {
1026 } elsif ( $genre eq 'journalArticle' ) {
1034 if ( C4::Context->preference("marcflavour") eq "UNIMARC" ) {
1037 $aulast = $record->subfield( '700', 'a' ) || '';
1038 $aufirst = $record->subfield( '700', 'b' ) || '';
1039 push @authors, "$aufirst $aulast" if ($aufirst or $aulast);
1042 if ( $record->field('200') ) {
1043 for my $au ( $record->field('200')->subfield('g') ) {
1048 $title = $record->subfield( '200', 'a' );
1049 my $subfield_210d = $record->subfield('210', 'd');
1050 if ($subfield_210d and $subfield_210d =~ /(\d{4})/) {
1053 $publisher = $record->subfield( '210', 'c' ) || '';
1054 $isbn = $record->subfield( '010', 'a' ) || '';
1055 $issn = $record->subfield( '011', 'a' ) || '';
1058 # MARC21 need some improve
1061 if ( $record->field('100') ) {
1062 push @authors, $record->subfield( '100', 'a' );
1066 if ( $record->field('700') ) {
1067 for my $au ( $record->field('700')->subfield('a') ) {
1071 $title = $record->field('245');
1072 $title &&= $title->as_string('ab');
1073 if ($titletype eq 'a') {
1074 $pubyear = $record->field('008') || '';
1075 $pubyear = substr($pubyear->data(), 7, 4) if $pubyear;
1076 $isbn = $record->subfield( '773', 'z' ) || '';
1077 $issn = $record->subfield( '773', 'x' ) || '';
1078 $hosttitle = $record->subfield( '773', 't' ) || $record->subfield( '773', 'a') || q{};
1079 my @rels = $record->subfield( '773', 'g' );
1080 $pages = join(', ', @rels);
1082 $pubyear = $record->subfield( '260', 'c' ) || '';
1083 $publisher = $record->subfield( '260', 'b' ) || '';
1084 $isbn = $record->subfield( '020', 'a' ) || '';
1085 $issn = $record->subfield( '022', 'a' ) || '';
1091 [ 'ctx_ver', 'Z39.88-2004' ],
1092 [ 'rft_val_fmt', "info:ofi/fmt:kev:mtx:$mtx" ],
1093 [ ($mtx eq 'dc' ? 'rft.type' : 'rft.genre'), $genre ],
1094 [ "rft.${titletype}title", $title ],
1097 # rft.title is authorized only once, so by checking $titletype
1098 # we ensure that rft.title is not already in the list.
1099 if ($hosttitle and $titletype) {
1100 push @params, [ 'rft.title', $hosttitle ];
1104 [ 'rft.isbn', $isbn ],
1105 [ 'rft.issn', $issn ],
1108 # If it's a subscription, these informations have no meaning.
1109 if ($genre ne 'journal') {
1111 [ 'rft.aulast', $aulast ],
1112 [ 'rft.aufirst', $aufirst ],
1113 (map { [ 'rft.au', $_ ] } @authors),
1114 [ 'rft.pub', $publisher ],
1115 [ 'rft.date', $pubyear ],
1116 [ 'rft.pages', $pages ],
1120 my $coins_value = join( '&',
1121 map { $$_[1] ? $$_[0] . '=' . uri_escape_utf8( $$_[1] ) : () } @params );
1123 return $coins_value;
1128 my $url = $biblio->get_openurl;
1130 Returns url for OpenURL resolver set in OpenURLResolverURL system preference
1137 my $OpenURLResolverURL = C4::Context->preference('OpenURLResolverURL');
1139 if ($OpenURLResolverURL) {
1140 my $uri = URI->new($OpenURLResolverURL);
1142 if (not defined $uri->query) {
1143 $OpenURLResolverURL .= '?';
1145 $OpenURLResolverURL .= '&';
1147 $OpenURLResolverURL .= $self->get_coins;
1150 return $OpenURLResolverURL;
1155 my $serial = $biblio->is_serial
1157 Return boolean true if this bibbliographic record is continuing resource
1164 return 1 if $self->serial;
1166 my $record = $self->metadata->record;
1167 return 1 if substr($record->leader, 7, 1) eq 's';
1172 =head3 custom_cover_image_url
1174 my $image_url = $biblio->custom_cover_image_url
1176 Return the specific url of the cover image for this bibliographic record.
1177 It is built regaring the value of the system preference CustomCoverImagesURL
1181 sub custom_cover_image_url {
1183 my $url = C4::Context->preference('CustomCoverImagesURL');
1184 if ( $url =~ m|{isbn}| ) {
1185 my $isbn = $self->biblioitem->isbn;
1186 return unless $isbn;
1187 $url =~ s|{isbn}|$isbn|g;
1189 if ( $url =~ m|{normalized_isbn}| ) {
1190 my $normalized_isbn = $self->normalized_isbn;
1191 return unless $normalized_isbn;
1192 $url =~ s|{normalized_isbn}|$normalized_isbn|g;
1194 if ( $url =~ m|{issn}| ) {
1195 my $issn = $self->biblioitem->issn;
1196 return unless $issn;
1197 $url =~ s|{issn}|$issn|g;
1200 my $re = qr|{(?<field>\d{3})(\$(?<subfield>.))?}|;
1201 if ( $url =~ $re ) {
1202 my $field = $+{field};
1203 my $subfield = $+{subfield};
1204 my $marc_record = $self->metadata->record;
1207 $value = $marc_record->subfield( $field, $subfield );
1209 my $controlfield = $marc_record->field($field);
1210 $value = $controlfield->data() if $controlfield;
1212 return unless $value;
1213 $url =~ s|$re|$value|;
1221 Return the cover images associated with this biblio.
1228 my $cover_images_rs = $self->_result->cover_images;
1229 return unless $cover_images_rs;
1230 return Koha::CoverImages->_new_from_dbic($cover_images_rs);
1233 =head3 get_marc_notes
1235 $marcnotesarray = $biblio->get_marc_notes({ opac => 1 });
1237 Get all notes from the MARC record and returns them in an array.
1238 The notes are stored in different fields depending on MARC flavour.
1239 MARC21 5XX $u subfields receive special attention as they are URIs.
1243 sub get_marc_notes {
1244 my ( $self, $params ) = @_;
1246 my $marcflavour = C4::Context->preference('marcflavour');
1247 my $opac = $params->{opac} // '0';
1248 my $interface = $params->{opac} ? 'opac' : 'intranet';
1250 my $record = $params->{record} // $self->metadata->record;
1251 my $record_processor = Koha::RecordProcessor->new(
1253 filters => [ 'ViewPolicy', 'ExpandCodedFields' ],
1255 interface => $interface,
1256 frameworkcode => $self->frameworkcode
1260 $record_processor->process($record);
1262 my $scope = $marcflavour eq "UNIMARC"? '3..': '5..';
1263 #MARC21 specs indicate some notes should be private if first indicator 0
1264 my %maybe_private = (
1272 my %hiddenlist = map { $_ => 1 }
1273 split( /,/, C4::Context->preference('NotesToHide'));
1276 foreach my $field ( $record->field($scope) ) {
1277 my $tag = $field->tag();
1278 next if $hiddenlist{ $tag };
1279 next if $opac && $maybe_private{$tag} && !$field->indicator(1);
1280 if( $marcflavour ne 'UNIMARC' && $field->subfield('u') ) {
1281 # Field 5XX$u always contains URI
1282 # Examples: 505u, 506u, 510u, 514u, 520u, 530u, 538u, 540u, 542u, 552u, 555u, 561u, 563u, 583u
1283 # We first push the other subfields, then all $u's separately
1284 # Leave further actions to the template (see e.g. opac-detail)
1286 join '', ( 'a' .. 't', 'v' .. 'z', '0' .. '9' ); # excl 'u'
1287 push @marcnotes, { marcnote => $field->as_string($othersub) };
1288 foreach my $sub ( $field->subfield('u') ) {
1289 $sub =~ s/^\s+|\s+$//g; # trim
1290 push @marcnotes, { marcnote => $sub, tag => $tag };
1293 push @marcnotes, { marcnote => $field->as_string(), tag => $tag };
1299 =head3 _get_marc_authors
1301 Private method to return the list of authors contained in the MARC record.
1302 See get get_marc_contributors and get_marc_authors for the public methods.
1306 sub _get_marc_authors {
1307 my ( $self, $params ) = @_;
1309 my $fields_filter = $params->{fields_filter};
1310 my $mintag = $params->{mintag};
1311 my $maxtag = $params->{maxtag};
1313 my $AuthoritySeparator = C4::Context->preference('AuthoritySeparator');
1314 my $marcflavour = C4::Context->preference('marcflavour');
1316 # tagslib useful only for UNIMARC author responsibilities
1317 my $tagslib = $marcflavour eq "UNIMARC"
1318 ? C4::Biblio::GetMarcStructure( 1, $self->frameworkcode, { unsafe => 1 } )
1322 foreach my $field ( $self->metadata->record->field($fields_filter) ) {
1325 if $mintag && $field->tag() < $mintag
1326 || $maxtag && $field->tag() > $maxtag;
1330 my @subfields = $field->subfields();
1333 # if there is an authority link, build the link with Koha-Auth-Number: subfield9
1334 my $subfield9 = $field->subfield('9');
1336 my $linkvalue = $subfield9;
1337 $linkvalue =~ s/(\(|\))//g;
1338 @link_loop = ( { 'limit' => 'an', 'link' => $linkvalue } );
1343 for my $authors_subfield (@subfields) {
1344 next if ( $authors_subfield->[0] eq '9' );
1346 # unimarc3 contains the $3 of the author for UNIMARC.
1347 # For french academic libraries, it's the "ppn", and it's required for idref webservice
1348 $unimarc3 = $authors_subfield->[1] if $marcflavour eq 'UNIMARC' and $authors_subfield->[0] =~ /3/;
1350 # don't load unimarc subfields 3, 5
1351 next if ( $marcflavour eq 'UNIMARC' and ( $authors_subfield->[0] =~ /3|5/ ) );
1353 my $code = $authors_subfield->[0];
1354 my $value = $authors_subfield->[1];
1355 my $linkvalue = $value;
1356 $linkvalue =~ s/(\(|\))//g;
1357 # UNIMARC author responsibility
1358 if ( $marcflavour eq 'UNIMARC' and $code eq '4' ) {
1359 $value = C4::Biblio::GetAuthorisedValueDesc( $field->tag(), $code, $value, '', $tagslib );
1360 $linkvalue = "($value)";
1362 # if no authority link, build a search query
1363 unless ($subfield9) {
1366 'link' => $linkvalue,
1367 operator => (scalar @link_loop) ? ' AND ' : undef
1370 my @this_link_loop = @link_loop;
1372 unless ( $code eq '0') {
1373 push @subfields_loop, {
1374 tag => $field->tag(),
1377 link_loop => \@this_link_loop,
1378 separator => (scalar @subfields_loop) ? $AuthoritySeparator : ''
1382 push @marcauthors, {
1383 MARCAUTHOR_SUBFIELDS_LOOP => \@subfields_loop,
1384 authoritylink => $subfield9,
1385 unimarc3 => $unimarc3
1388 return \@marcauthors;
1391 =head3 get_marc_contributors
1393 my $contributors = $biblio->get_marc_contributors;
1395 Get all contributors (but first author) from the MARC record and returns them in an array.
1396 They are stored in different fields depending on MARC flavour (700..720 for MARC21)
1400 sub get_marc_contributors {
1401 my ( $self, $params ) = @_;
1403 my ( $mintag, $maxtag, $fields_filter );
1404 my $marcflavour = C4::Context->preference('marcflavour');
1406 if ( $marcflavour eq "UNIMARC" ) {
1409 $fields_filter = '7..';
1410 } else { # marc21/normarc
1413 $fields_filter = '7..';
1416 return $self->_get_marc_authors(
1418 fields_filter => $fields_filter,
1425 =head3 get_marc_authors
1427 my $authors = $biblio->get_marc_authors;
1429 Get all authors from the MARC record and returns them in an array.
1430 They are stored in different fields depending on MARC flavour
1431 (main author from 100 then secondary authors from 700..720).
1435 sub get_marc_authors {
1436 my ( $self, $params ) = @_;
1438 my ( $mintag, $maxtag, $fields_filter );
1439 my $marcflavour = C4::Context->preference('marcflavour');
1441 if ( $marcflavour eq "UNIMARC" ) {
1442 $fields_filter = '200';
1443 } else { # marc21/normarc
1444 $fields_filter = '100';
1447 my @first_authors = @{$self->_get_marc_authors(
1449 fields_filter => $fields_filter,
1455 my @other_authors = @{$self->get_marc_contributors};
1457 return [@first_authors, @other_authors];
1460 =head3 normalized_isbn
1462 my $normalized_isbn = $biblio->normalized_isbn
1464 Normalizes and returns the first valid ISBN found in the record.
1465 ISBN13 are converted into ISBN10. This is required to get some book cover images.
1469 sub normalized_isbn {
1471 return C4::Koha::GetNormalizedISBN($self->biblioitem->isbn);
1474 =head3 public_read_list
1476 This method returns the list of publicly readable database fields for both API and UI output purposes
1480 sub public_read_list {
1482 'biblionumber', 'frameworkcode', 'author',
1483 'title', 'medium', 'subtitle',
1484 'part_number', 'part_name', 'unititle',
1485 'notes', 'serial', 'seriestitle',
1486 'copyrightdate', 'abstract'
1490 =head3 metadata_extractor
1492 my $extractor = $biblio->metadata_extractor
1494 Return a Koha::Biblio::Metadata::Extractor object to use to extract data from the metadata (ie. MARC record for now)
1498 sub metadata_extractor {
1501 $self->{metadata_extractor} ||= Koha::Biblio::Metadata::Extractor->new( { biblio => $self } );
1503 return $self->{metadata_extractor};
1506 =head3 normalized_upc
1508 my $normalized_upc = $biblio->normalized_upc
1510 Normalizes and returns the UPC value found in the MARC record.
1514 sub normalized_upc {
1516 return $self->metadata_extractor->get_normalized_upc;
1519 =head3 normalized_oclc
1521 my $normalized_oclc = $biblio->normalized_oclc
1523 Normalizes and returns the OCLC number found in the MARC record.
1527 sub normalized_oclc {
1529 return $self->metadata_extractor->get_normalized_oclc;
1534 my $json = $biblio->to_api;
1536 Overloaded method that returns a JSON representation of the Koha::Biblio object,
1537 suitable for API output. The related Koha::Biblioitem object is merged as expected
1543 my ($self, $args) = @_;
1545 my $json_biblio = $self->SUPER::to_api( $args );
1546 return unless $json_biblio;
1548 $args = defined $args ? {%$args} : {};
1549 delete $args->{embed};
1551 my $json_biblioitem = $self->biblioitem->to_api( $args );
1552 return unless $json_biblioitem;
1554 return { %$json_biblio, %$json_biblioitem };
1557 =head3 to_api_mapping
1559 This method returns the mapping for representing a Koha::Biblio object
1564 sub to_api_mapping {
1566 biblionumber => 'biblio_id',
1567 frameworkcode => 'framework_id',
1568 unititle => 'uniform_title',
1569 seriestitle => 'series_title',
1570 copyrightdate => 'copyright_date',
1571 datecreated => 'creation_date',
1572 deleted_on => undef,
1576 =head3 get_marc_host
1578 $host = $biblio->get_marc_host;
1580 ( $host, $relatedparts, $hostinfo ) = $biblio->get_marc_host;
1582 Returns host biblio record from MARC21 773 (undef if no 773 present).
1583 It looks at the first 773 field with MARCorgCode or only a control
1584 number. Complete $w or numeric part is used to search host record.
1585 The optional parameter no_items triggers a check if $biblio has items.
1586 If there are, the sub returns undef.
1587 Called in list context, it also returns 773$g (related parts).
1589 If there is no $w, we use $0 (host biblionumber) or $9 (host itemnumber)
1590 to search for the host record. If there is also no $0 and no $9, we search
1591 using author and title. Failing all of that, we return an undef host and
1592 form a concatenation of strings with 773$agt for host information,
1593 returned when called in list context.
1598 my ($self, $params) = @_;
1599 my $no_items = $params->{no_items};
1600 return if C4::Context->preference('marcflavour') eq 'UNIMARC'; # TODO
1601 return if $params->{no_items} && $self->items->count > 0;
1604 eval { $record = $self->metadata->record };
1607 # We pick the first $w with your MARCOrgCode or the first $w that has no
1608 # code (between parentheses) at all.
1609 my $orgcode = C4::Context->preference('MARCOrgCode') // q{};
1611 foreach my $f ( $record->field('773') ) {
1612 my $w = $f->subfield('w') or next;
1613 if( $w =~ /^\($orgcode\)\s*(\d+)/i or $w =~ /^\d+/ ) {
1619 my $engine = Koha::SearchEngine::Search->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
1621 if ( !$hostfld and $record->subfield('773','t') ) {
1622 # not linked using $w
1623 my $unlinkedf = $record->field('773');
1625 if ( C4::Context->preference("EasyAnalyticalRecords") ) {
1626 if ( $unlinkedf->subfield('0') ) {
1627 # use 773$0 host biblionumber
1628 $bibno = $unlinkedf->subfield('0');
1629 } elsif ( $unlinkedf->subfield('9') ) {
1630 # use 773$9 host itemnumber
1631 my $linkeditemnumber = $unlinkedf->subfield('9');
1632 $bibno = Koha::Items->find( $linkeditemnumber )->biblionumber;
1636 my $host = Koha::Biblios->find($bibno) or return;
1637 return wantarray ? ( $host, $unlinkedf->subfield('g') ) : $host;
1639 # just return plaintext and no host record
1640 my $hostinfo = join( ", ", $unlinkedf->subfield('a'), $unlinkedf->subfield('t'), $unlinkedf->subfield('g') );
1641 return wantarray ? ( undef, $unlinkedf->subfield('g'), $hostinfo ) : undef;
1643 return if !$hostfld;
1644 my $rcn = $hostfld->subfield('w');
1646 # Look for control number with/without orgcode
1647 for my $try (1..2) {
1648 my ( $error, $results, $total_hits ) = $engine->simple_search_compat( 'Control-number='.$rcn, 0,1 );
1649 if( !$error and $total_hits == 1 ) {
1650 $bibno = $engine->extract_biblionumber( $results->[0] );
1653 # Add or remove orgcode for second try
1654 if( $try == 1 && $rcn =~ /\)\s*(\d+)/ ) {
1655 $rcn = $1; # number only
1656 } elsif( $try == 1 && $rcn =~ /^\d+/ ) {
1657 $rcn = "($orgcode)$rcn";
1663 my $host = Koha::Biblios->find($bibno) or return;
1664 return wantarray ? ( $host, $hostfld->subfield('g') ) : $host;
1668 =head3 get_marc_host_only
1670 my $host = $biblio->get_marc_host_only;
1676 sub get_marc_host_only {
1679 my ( $host ) = $self->get_marc_host;
1684 =head3 get_marc_relatedparts_only
1686 my $relatedparts = $biblio->get_marc_relatedparts_only;
1688 Return related parts only
1692 sub get_marc_relatedparts_only {
1695 my ( undef, $relatedparts ) = $self->get_marc_host;
1697 return $relatedparts;
1700 =head3 get_marc_hostinfo_only
1702 my $hostinfo = $biblio->get_marc_hostinfo_only;
1704 Return host info only
1708 sub get_marc_hostinfo_only {
1711 my ( $host, $relatedparts, $hostinfo ) = $self->get_marc_host;
1718 my $recalls = $biblio->recalls;
1720 Return recalls linked to this biblio
1726 return Koha::Recalls->_new_from_dbic( scalar $self->_result->recalls );
1729 =head3 can_be_recalled
1731 my @items_for_recall = $biblio->can_be_recalled({ patron => $patron_object });
1733 Does biblio-level checks and returns the items attached to this biblio that are available for recall
1737 sub can_be_recalled {
1738 my ( $self, $params ) = @_;
1740 return 0 if !( C4::Context->preference('UseRecalls') );
1742 my $patron = $params->{patron};
1744 my $branchcode = C4::Context->userenv->{'branch'};
1745 if ( C4::Context->preference('CircControl') eq 'PatronLibrary' and $patron ) {
1746 $branchcode = $patron->branchcode;
1749 my @all_items = Koha::Items->search({ biblionumber => $self->biblionumber })->as_list;
1751 # if there are no available items at all, no recall can be placed
1752 return 0 if ( scalar @all_items == 0 );
1757 my @all_itemnumbers;
1758 foreach my $item ( @all_items ) {
1759 push( @all_itemnumbers, $item->itemnumber );
1760 if ( $item->can_be_recalled({ patron => $patron }) ) {
1761 push( @itemtypes, $item->effective_itemtype );
1762 push( @itemnumbers, $item->itemnumber );
1763 push( @items, $item );
1767 # if there are no recallable items, no recall can be placed
1768 return 0 if ( scalar @items == 0 );
1770 # Check the circulation rule for each relevant itemtype for this biblio
1771 my ( @recalls_allowed, @recalls_per_record, @on_shelf_recalls );
1772 foreach my $itemtype ( @itemtypes ) {
1773 my $rule = Koha::CirculationRules->get_effective_rules({
1774 branchcode => $branchcode,
1775 categorycode => $patron ? $patron->categorycode : undef,
1776 itemtype => $itemtype,
1779 'recalls_per_record',
1783 push( @recalls_allowed, $rule->{recalls_allowed} ) if $rule;
1784 push( @recalls_per_record, $rule->{recalls_per_record} ) if $rule;
1785 push( @on_shelf_recalls, $rule->{on_shelf_recalls} ) if $rule;
1787 my $recalls_allowed = (sort {$b <=> $a} @recalls_allowed)[0]; # take highest
1788 my $recalls_per_record = (sort {$b <=> $a} @recalls_per_record)[0]; # take highest
1789 my %on_shelf_recalls_count = ();
1790 foreach my $count ( @on_shelf_recalls ) {
1791 $on_shelf_recalls_count{$count}++;
1793 my $on_shelf_recalls = (sort {$on_shelf_recalls_count{$b} <=> $on_shelf_recalls_count{$a}} @on_shelf_recalls)[0]; # take most common
1795 # check recalls allowed has been set and is not zero
1796 return 0 if ( !defined($recalls_allowed) || $recalls_allowed == 0 );
1799 # check borrower has not reached open recalls allowed limit
1800 return 0 if ( $patron->recalls->filter_by_current->count >= $recalls_allowed );
1802 # check borrower has not reached open recalls allowed per record limit
1803 return 0 if ( $patron->recalls->filter_by_current->search({ biblio_id => $self->biblionumber })->count >= $recalls_per_record );
1805 # check if any of the items under this biblio are already checked out by this borrower
1806 return 0 if ( Koha::Checkouts->search({ itemnumber => [ @all_itemnumbers ], borrowernumber => $patron->borrowernumber })->count > 0 );
1809 # check item availability
1810 my $checked_out_count = 0;
1812 if ( Koha::Checkouts->search({ itemnumber => $_->itemnumber })->count > 0 ){ $checked_out_count++; }
1815 # can't recall if on shelf recalls only allowed when all unavailable, but items are still available for checkout
1816 return 0 if ( $on_shelf_recalls eq 'all' && $checked_out_count < scalar @items );
1818 # can't recall if no items have been checked out
1819 return 0 if ( $checked_out_count == 0 );
1827 my $ratings = $biblio->ratings
1829 Return a Koha::Ratings object representing the ratings of this bibliographic record
1835 my $rs = $self->_result->ratings;
1836 return Koha::Ratings->_new_from_dbic($rs);
1839 =head3 opac_summary_html
1841 my $summary_html = $biblio->opac_summary_html
1843 Based on the syspref OPACMySummaryHTML, returns a string representing the
1844 summary of this bibliographic record.
1845 {AUTHOR}, {TITLE}, {ISBN} and {BIBLIONUMBER} will be replaced.
1849 sub opac_summary_html {
1852 my $summary_html = C4::Context->preference('OPACMySummaryHTML');
1853 return q{} unless $summary_html;
1854 my $author = $self->author || q{};
1855 my $title = $self->title || q{};
1856 $title =~ s/\/+$//; # remove trailing slash
1857 $title =~ s/\s+$//; # remove trailing space
1858 my $normalized_isbn = $self->normalized_isbn || q{};
1859 my $biblionumber = $self->biblionumber;
1861 $summary_html =~ s/{AUTHOR}/$author/g;
1862 $summary_html =~ s/{TITLE}/$title/g;
1863 $summary_html =~ s/{ISBN}/$normalized_isbn/g;
1864 $summary_html =~ s/{BIBLIONUMBER}/$biblionumber/g;
1866 return $summary_html;
1871 my $biblio = Koha::Biblios->find($biblionumber);
1872 $biblio->merge_with(\@biblio_ids);
1874 This subroutine merges a list of bibliographic records into the bibliographic record.
1875 This function DOES NOT CHANGE the bibliographic metadata of the record. But it links all
1876 items, holds, subscriptions, serials issues and article_requests to the record. After doing changes
1877 bibliographic records listed are deleted
1882 my ( $self, $biblio_ids ) = @_;
1884 my $schema = Koha::Database->new()->schema();
1885 my $ref_biblionumber = $self->biblionumber;
1886 my %results = ( 'biblio_id' => $ref_biblionumber, 'merged_ids' => [] );
1888 # Ensure the keeper isn't in the list of records to merge
1889 my @biblio_ids_to_merge = grep { $_ ne $ref_biblionumber } @$biblio_ids;
1894 foreach my $bn_merge (@biblio_ids_to_merge) {
1895 my $from_biblio = Koha::Biblios->find($bn_merge);
1896 $from_biblio->items->move_to_biblio($self);
1898 # Move article requests
1899 $from_biblio->article_requests->update(
1900 { biblionumber => $ref_biblionumber },
1901 { no_triggers => 1 }
1904 # Move subscriptions
1905 $from_biblio->subscriptions->update(
1906 { biblionumber => $ref_biblionumber },
1907 { no_triggers => 1 }
1910 # Move subscription histories
1911 $from_biblio->subscription_histories->update(
1912 { biblionumber => $ref_biblionumber },
1913 { no_triggers => 1 }
1917 $from_biblio->serials->update(
1918 { biblionumber => $ref_biblionumber },
1919 { no_triggers => 1 }
1923 $from_biblio->suggestions->update(
1924 { biblionumber => $ref_biblionumber },
1925 { no_triggers => 1 }
1928 my $orders = $from_biblio->orders->unblessed;
1929 for my $order (@$orders) {
1930 $order->{'biblionumber'} = $ref_biblionumber;
1932 # TODO Do we really need to call ModOrder here?
1935 # TODO : add error control (in ModOrder?)
1939 MergeHolds( $schema->storage->dbh, $ref_biblionumber, $bn_merge );
1941 my $error = DelBiblio($bn_merge); #DelBiblio return undef unless an error occurs
1945 push( @{ $results{merged_ids} }, $bn_merge );
1951 Koha::Exception->throw($_);
1956 =head2 Internal methods
1968 Kyle M Hall <kyle@bywatersolutions.com>