+=head3 get_marc_host
+
+ $host = $biblio->get_marc_host;
+ # OR:
+ ( $host, $relatedparts, $hostinfo ) = $biblio->get_marc_host;
+
+ Returns host biblio record from MARC21 773 (undef if no 773 present).
+ It looks at the first 773 field with MARCorgCode or only a control
+ number. Complete $w or numeric part is used to search host record.
+ The optional parameter no_items triggers a check if $biblio has items.
+ If there are, the sub returns undef.
+ Called in list context, it also returns 773$g (related parts).
+
+ If there is no $w, we use $0 (host biblionumber) or $9 (host itemnumber)
+ to search for the host record. If there is also no $0 and no $9, we search
+ using author and title. Failing all of that, we return an undef host and
+ form a concatenation of strings with 773$agt for host information,
+ returned when called in list context.
+
+=cut
+
+sub get_marc_host {
+ my ($self, $params) = @_;
+ my $no_items = $params->{no_items};
+ return if C4::Context->preference('marcflavour') eq 'UNIMARC'; # TODO
+ return if $params->{no_items} && $self->items->count > 0;
+
+ my $record;
+ eval { $record = $self->metadata->record };
+ return if !$record;
+
+ # We pick the first $w with your MARCOrgCode or the first $w that has no
+ # code (between parentheses) at all.
+ my $orgcode = C4::Context->preference('MARCOrgCode') // q{};
+ my $hostfld;
+ foreach my $f ( $record->field('773') ) {
+ my $w = $f->subfield('w') or next;
+ if( $w =~ /^\($orgcode\)\s*(\d+)/i or $w =~ /^\d+/ ) {
+ $hostfld = $f;
+ last;
+ }
+ }
+
+ my $engine = Koha::SearchEngine::Search->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
+ my $bibno;
+ if ( !$hostfld and $record->subfield('773','t') ) {
+ # not linked using $w
+ my $unlinkedf = $record->field('773');
+ my $host;
+ if ( C4::Context->preference("EasyAnalyticalRecords") ) {
+ if ( $unlinkedf->subfield('0') ) {
+ # use 773$0 host biblionumber
+ $bibno = $unlinkedf->subfield('0');
+ } elsif ( $unlinkedf->subfield('9') ) {
+ # use 773$9 host itemnumber
+ my $linkeditemnumber = $unlinkedf->subfield('9');
+ $bibno = Koha::Items->find( $linkeditemnumber )->biblionumber;
+ }
+ }
+ if ( $bibno ) {
+ my $host = Koha::Biblios->find($bibno) or return;
+ return wantarray ? ( $host, $unlinkedf->subfield('g') ) : $host;
+ }
+ # just return plaintext and no host record
+ my $hostinfo = join( ", ", $unlinkedf->subfield('a'), $unlinkedf->subfield('t'), $unlinkedf->subfield('g') );
+ return wantarray ? ( undef, $unlinkedf->subfield('g'), $hostinfo ) : undef;
+ }
+ return if !$hostfld;
+ my $rcn = $hostfld->subfield('w');
+
+ # Look for control number with/without orgcode
+ for my $try (1..2) {
+ my ( $error, $results, $total_hits ) = $engine->simple_search_compat( 'Control-number='.$rcn, 0,1 );
+ if( !$error and $total_hits == 1 ) {
+ $bibno = $engine->extract_biblionumber( $results->[0] );
+ last;
+ }
+ # Add or remove orgcode for second try
+ if( $try == 1 && $rcn =~ /\)\s*(\d+)/ ) {
+ $rcn = $1; # number only
+ } elsif( $try == 1 && $rcn =~ /^\d+/ ) {
+ $rcn = "($orgcode)$rcn";
+ } else {
+ last;
+ }
+ }
+ if( $bibno ) {
+ my $host = Koha::Biblios->find($bibno) or return;
+ return wantarray ? ( $host, $hostfld->subfield('g') ) : $host;
+ }
+}
+
+=head3 recalls
+
+ my $recalls = $biblio->recalls;
+
+Return recalls linked to this biblio
+
+=cut
+
+sub recalls {
+ my ( $self ) = @_;
+ return Koha::Recalls->_new_from_dbic( scalar $self->_result->recalls );
+}
+
+=head3 can_be_recalled
+
+ my @items_for_recall = $biblio->can_be_recalled({ patron => $patron_object });
+
+Does biblio-level checks and returns the items attached to this biblio that are available for recall
+
+=cut
+
+sub can_be_recalled {
+ my ( $self, $params ) = @_;
+
+ return 0 if !( C4::Context->preference('UseRecalls') );
+
+ my $patron = $params->{patron};
+
+ my $branchcode = C4::Context->userenv->{'branch'};
+ if ( C4::Context->preference('CircControl') eq 'PatronLibrary' and $patron ) {
+ $branchcode = $patron->branchcode;
+ }
+
+ my @all_items = Koha::Items->search({ biblionumber => $self->biblionumber })->as_list;
+
+ # if there are no available items at all, no recall can be placed
+ return 0 if ( scalar @all_items == 0 );
+
+ my @itemtypes;
+ my @itemnumbers;
+ my @items;
+ my @all_itemnumbers;
+ foreach my $item ( @all_items ) {
+ push( @all_itemnumbers, $item->itemnumber );
+ if ( $item->can_be_recalled({ patron => $patron }) ) {
+ push( @itemtypes, $item->effective_itemtype );
+ push( @itemnumbers, $item->itemnumber );
+ push( @items, $item );
+ }
+ }
+
+ # if there are no recallable items, no recall can be placed
+ return 0 if ( scalar @items == 0 );
+
+ # Check the circulation rule for each relevant itemtype for this biblio
+ my ( @recalls_allowed, @recalls_per_record, @on_shelf_recalls );
+ foreach my $itemtype ( @itemtypes ) {
+ my $rule = Koha::CirculationRules->get_effective_rules({
+ branchcode => $branchcode,
+ categorycode => $patron ? $patron->categorycode : undef,
+ itemtype => $itemtype,
+ rules => [
+ 'recalls_allowed',
+ 'recalls_per_record',
+ 'on_shelf_recalls',
+ ],
+ });
+ push( @recalls_allowed, $rule->{recalls_allowed} ) if $rule;
+ push( @recalls_per_record, $rule->{recalls_per_record} ) if $rule;
+ push( @on_shelf_recalls, $rule->{on_shelf_recalls} ) if $rule;
+ }
+ my $recalls_allowed = (sort {$b <=> $a} @recalls_allowed)[0]; # take highest
+ my $recalls_per_record = (sort {$b <=> $a} @recalls_per_record)[0]; # take highest
+ my %on_shelf_recalls_count = ();
+ foreach my $count ( @on_shelf_recalls ) {
+ $on_shelf_recalls_count{$count}++;
+ }
+ my $on_shelf_recalls = (sort {$on_shelf_recalls_count{$b} <=> $on_shelf_recalls_count{$a}} @on_shelf_recalls)[0]; # take most common
+
+ # check recalls allowed has been set and is not zero
+ return 0 if ( !defined($recalls_allowed) || $recalls_allowed == 0 );
+
+ if ( $patron ) {
+ # check borrower has not reached open recalls allowed limit
+ return 0 if ( $patron->recalls->filter_by_current->count >= $recalls_allowed );
+
+ # check borrower has not reached open recalls allowed per record limit
+ return 0 if ( $patron->recalls->filter_by_current->search({ biblio_id => $self->biblionumber })->count >= $recalls_per_record );
+
+ # check if any of the items under this biblio are already checked out by this borrower
+ return 0 if ( Koha::Checkouts->search({ itemnumber => [ @all_itemnumbers ], borrowernumber => $patron->borrowernumber })->count > 0 );
+ }
+
+ # check item availability
+ my $checked_out_count = 0;
+ foreach (@items) {
+ if ( Koha::Checkouts->search({ itemnumber => $_->itemnumber })->count > 0 ){ $checked_out_count++; }
+ }
+
+ # can't recall if on shelf recalls only allowed when all unavailable, but items are still available for checkout
+ return 0 if ( $on_shelf_recalls eq 'all' && $checked_out_count < scalar @items );
+
+ # can't recall if no items have been checked out
+ return 0 if ( $checked_out_count == 0 );
+
+ # can recall
+ return @items;
+}
+