1 package Koha::REST::V1::Biblios;
3 # This file is part of Koha.
5 # Koha is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
10 # Koha is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with Koha; if not, see <http://www.gnu.org/licenses>.
20 use Mojo::Base 'Mojolicious::Controller';
25 use Koha::RecordProcessor;
26 use C4::Biblio qw( DelBiblio AddBiblio ModBiblio );
27 use C4::Search qw( FindDuplicate );
29 use C4::Barcodes::ValueBuilder;
34 use List::MoreUtils qw( any );
35 use MARC::Record::MiJ;
37 use Try::Tiny qw( catch try );
45 Controller function that handles retrieving a single biblio object
50 my $c = shift->openapi->valid_input or return;
53 $attributes = { prefetch => [ 'metadata' ] } # don't prefetch metadata if not needed
54 unless $c->req->headers->accept =~ m/application\/json/;
56 my $biblio = Koha::Biblios->find( { biblionumber => $c->validation->param('biblio_id') }, $attributes );
62 error => "Object not found."
69 if ( $c->req->headers->accept =~ m/application\/json/ ) {
72 json => $biblio->to_api
76 my $metadata = $biblio->metadata;
77 my $record = $metadata->record;
78 my $schema = $metadata->schema // C4::Context->preference("marcflavour");
84 text => $record->as_xml_record($schema),
89 data => $record->to_mij
94 text => $record->as_usmarc
98 format => 'text/plain',
99 text => $record->as_formatted
105 "application/marcxml+xml",
106 "application/marc-in-json",
115 $c->unhandled_exception($_);
121 Controller function that handles deleting a biblio object
126 my $c = shift->openapi->valid_input or return;
128 my $biblio = Koha::Biblios->find( $c->validation->param('biblio_id') );
130 if ( not defined $biblio ) {
133 openapi => { error => "Object not found" }
138 my $error = DelBiblio( $biblio->id );
143 openapi => { error => $error }
147 return $c->render( status => 204, openapi => "" );
151 $c->unhandled_exception($_);
157 Controller function that handles retrieving a single biblio object
162 my $c = shift->openapi->valid_input or return;
164 my $biblio = Koha::Biblios->find(
165 { biblionumber => $c->validation->param('biblio_id') },
166 { prefetch => ['metadata'] } );
172 error => "Object not found."
179 my $metadata = $biblio->metadata;
180 my $record = $metadata->record;
182 my $opachiddenitems_rules = C4::Context->yaml_preference('OpacHiddenItems');
183 my $patron = $c->stash('koha.user');
185 # Check if the biblio should be hidden for unprivileged access
186 # unless there's a logged in user, and there's an exception for it's
188 unless ( $patron and $patron->category->override_hidden_items ) {
189 if ( $biblio->hidden_in_opac({ rules => $opachiddenitems_rules }) )
194 error => "Object not found."
200 my $schema = $metadata->schema // C4::Context->preference("marcflavour");
202 my $record_processor = Koha::RecordProcessor->new({
203 filters => 'ViewPolicy',
206 frameworkcode => $biblio->frameworkcode
209 # Apply framework's filtering to MARC::Record object
210 $record_processor->process($record);
216 text => $record->as_xml_record($schema),
221 data => $record->to_mij
226 text => $record->as_usmarc
230 format => 'text/plain',
231 text => $record->as_formatted
236 "application/marcxml+xml",
237 "application/marc-in-json",
245 $c->unhandled_exception($_);
251 Controller function that handles retrieving biblio's items
256 my $c = shift->openapi->valid_input or return;
258 my $biblio = Koha::Biblios->find( { biblionumber => $c->validation->param('biblio_id') }, { prefetch => ['items'] } );
264 error => "Object not found."
271 my $items_rs = $biblio->items;
272 my $items = $c->objects->search( $items_rs );
279 $c->unhandled_exception($_);
285 Controller function that handles creating a biblio's item
290 my $c = shift->openapi->valid_input or return;
293 my $biblio_id = $c->validation->param('biblio_id');
294 my $biblio = Koha::Biblios->find( $biblio_id );
299 openapi => { error => "Biblio not found" }
303 my $body = $c->validation->param('body');
305 $body->{biblio_id} = $biblio_id;
307 # Don't save extended subfields yet. To be done in another bug.
308 $body->{extended_subfields} = undef;
310 my $item = Koha::Item->new_from_api($body);
312 if ( !defined $item->barcode ) {
314 # FIXME This should be moved to Koha::Item->store
315 my $autoBarcode = C4::Context->preference('autoBarcode');
318 if ( !$autoBarcode || $autoBarcode eq 'OFF' ) {
321 elsif ( $autoBarcode eq 'incremental' ) {
323 C4::Barcodes::ValueBuilder::incremental::get_barcode;
325 elsif ( $autoBarcode eq 'annual' ) {
326 my $year = Koha::DateUtils::dt_from_string()->year();
328 C4::Barcodes::ValueBuilder::annual::get_barcode(
331 elsif ( $autoBarcode eq 'hbyymmincr' ) {
333 # Generates a barcode where
334 # hb = home branch Code,
335 # yymm = year/month catalogued,
336 # incr = incremental number,
337 # reset yearly -fbcit
338 my $now = Koha::DateUtils::dt_from_string();
339 my $year = $now->year();
340 my $month = $now->month();
341 my $homebranch = $item->homebranch // '';
343 C4::Barcodes::ValueBuilder::hbyymmincr::get_barcode(
344 { year => $year, mon => $month } );
345 $barcode = $homebranch . $barcode;
347 elsif ( $autoBarcode eq 'EAN13' ) {
349 # not the best, two catalogers could add the same
350 # barcode easily this way :/
351 my $query = "select max(abs(barcode)) from items";
352 my $dbh = C4::Context->dbh;
353 my $sth = $dbh->prepare($query);
356 while ( my ($last) = $sth->fetchrow_array ) {
359 my $ean = CheckDigits('ean');
360 if ( $ean->is_valid($nextnum) ) {
361 my $next = $ean->basenumber($nextnum) + 1;
362 $nextnum = $ean->complete($next);
364 '0' x ( 13 - length($nextnum) ) . $nextnum; # pad zeros
367 warn "ERROR: invalid EAN-13 $nextnum, using increment";
373 warn "ERROR: unknown autoBarcode: $autoBarcode";
375 $item->barcode($barcode) if $barcode;
378 $item->store->discard_changes;
382 openapi => $item->to_api
386 if ( blessed $_ and $_->isa('Koha::Exceptions::Object::DuplicateID') ) {
389 openapi => { error => 'Duplicate barcode.' }
392 $c->unhandled_exception($_);
398 Controller function that handles updating a biblio's item
403 my $c = shift->openapi->valid_input or return;
406 my $biblio_id = $c->validation->param('biblio_id');
407 my $item_id = $c->validation->param('item_id');
408 my $biblio = Koha::Biblios->find({ biblionumber => $biblio_id });
412 openapi => { error => "Biblio not found" }
416 my $item = $biblio->items->find({ itemnumber => $item_id });
421 openapi => { error => "Item not found" }
425 my $body = $c->validation->param('body');
427 $body->{biblio_id} = $biblio_id;
429 # Don't save extended subfields yet. To be done in another bug.
430 $body->{extended_subfields} = undef;
432 $item->set_from_api($body);
434 $item->store->discard_changes;
438 openapi => $item->to_api
442 if ( blessed $_ and $_->isa('Koha::Exceptions::Object::DuplicateID') ) {
445 openapi => { error => 'Duplicate barcode.' }
448 $c->unhandled_exception($_);
454 List Koha::Checkout objects
459 my $c = shift->openapi->valid_input or return;
461 my $checked_in = delete $c->validation->output->{checked_in};
464 my $biblio = Koha::Biblios->find( $c->validation->param('biblio_id') );
469 openapi => { error => 'Object not found' }
475 ? $c->objects->search( $biblio->old_checkouts )
476 : $c->objects->search( $biblio->current_checkouts );
480 openapi => $checkouts
484 $c->unhandled_exception($_);
488 =head3 pickup_locations
490 Method that returns the possible pickup_locations for a given biblio
491 used for building the dropdown selector
495 sub pickup_locations {
496 my $c = shift->openapi->valid_input or return;
498 my $biblio_id = $c->validation->param('biblio_id');
499 my $biblio = Koha::Biblios->find( $biblio_id );
504 openapi => { error => "Biblio not found" }
508 my $patron_id = delete $c->validation->output->{patron_id};
509 my $patron = Koha::Patrons->find( $patron_id );
514 openapi => { error => "Patron not found" }
520 my $pl_set = $biblio->pickup_locations( { patron => $patron } );
523 if ( C4::Context->preference('AllowHoldPolicyOverride') ) {
525 my $libraries_rs = Koha::Libraries->search( { pickup_location => 1 } );
526 my $libraries = $c->objects->search($libraries_rs);
530 $library->{needs_override} = (
531 any { $_->branchcode eq $library->{library_id} }
541 my $pickup_locations = $c->objects->search($pl_set);
542 @response = map { $_->{needs_override} = Mojo::JSON->false; $_; } @{$pickup_locations};
547 openapi => \@response
551 $c->unhandled_exception($_);
555 =head3 get_items_public
557 Controller function that handles retrieving biblio's items, for unprivileged
562 sub get_items_public {
563 my $c = shift->openapi->valid_input or return;
565 my $biblio = Koha::Biblios->find( { biblionumber => $c->validation->param('biblio_id') }, { prefetch => ['items'] } );
571 error => "Object not found."
578 my $patron = $c->stash('koha.user');
580 my $items_rs = $biblio->items->filter_by_visible_in_opac({ patron => $patron });
581 my $items = $c->objects->search( $items_rs );
588 $c->unhandled_exception($_);
594 Set rating for the logged in user
600 my $c = shift->openapi->valid_input or return;
602 my $biblio = Koha::Biblios->find( $c->validation->param('biblio_id') );
608 error => "Object not found."
613 my $patron = $c->stash('koha.user');
618 { error => "Cannot rate. Reason: must be logged-in" }
622 my $body = $c->validation->param('body');
623 my $rating_value = $body->{rating};
627 my $rating = Koha::Ratings->find(
629 biblionumber => $biblio->biblionumber,
630 borrowernumber => $patron->borrowernumber,
633 $rating->delete if $rating;
635 if ( $rating_value ) { # Cannot set to 0 from the UI
636 $rating = Koha::Rating->new(
638 biblionumber => $biblio->biblionumber,
639 borrowernumber => $patron->borrowernumber,
640 rating_value => $rating_value,
645 Koha::Ratings->search( { biblionumber => $biblio->biblionumber } );
646 my $average = $ratings->get_avg_rating;
651 rating => $rating && $rating->in_storage ? $rating->rating_value : undef,
653 count => $ratings->count
658 $c->unhandled_exception($_);
664 Controller function that handles creating a biblio object
669 my $c = shift->openapi->valid_input or return;
672 my $headers = $c->req->headers;
674 my $flavour = $headers->header('x-record-schema');
675 $flavour //= C4::Context->preference('marcflavour');
679 my $frameworkcode = $headers->header('x-framework-id');
680 my $content_type = $headers->content_type;
682 if ( $content_type =~ m/application\/marcxml\+xml/ ) {
683 $record = MARC::Record->new_from_xml( $c->req->body, 'UTF-8', $flavour );
685 elsif ( $content_type =~ m/application\/marc-in-json/ ) {
686 $record = MARC::Record->new_from_mij_structure( $c->req->json );
688 elsif ( $content_type =~ m/application\/marc/ ) {
689 $record = MARC::Record->new_from_usmarc( $c->req->body );
695 "application/marcxml+xml",
696 "application/marc-in-json",
702 my ( $duplicatebiblionumber, $duplicatetitle );
703 ( $duplicatebiblionumber, $duplicatetitle ) = FindDuplicate($record);
705 my $confirm_not_duplicate = $headers->header('x-confirm-not-duplicate');
710 error => "Duplicate biblio $duplicatebiblionumber",
712 ) unless !$duplicatebiblionumber || $confirm_not_duplicate;
714 my ( $biblionumber, $oldbibitemnum );
715 ( $biblionumber, $oldbibitemnum ) = AddBiblio( $record, $frameworkcode );
719 openapi => { id => $biblionumber }
723 $c->unhandled_exception($_);
729 Controller function that handles modifying an biblio object
734 my $c = shift->openapi->valid_input or return;
736 my $biblio_id = $c->param('biblio_id');
737 my $biblio = Koha::Biblios->find($biblio_id);
739 if ( ! defined $biblio ) {
742 openapi => { error => "Object not found" }
747 my $headers = $c->req->headers;
749 my $flavour = $headers->header('x-record-schema');
750 $flavour //= C4::Context->preference('marcflavour');
752 my $frameworkcode = $headers->header('x-framework-id') || $biblio->frameworkcode;
754 my $content_type = $headers->content_type;
758 if ( $content_type =~ m/application\/marcxml\+xml/ ) {
759 $record = MARC::Record->new_from_xml( $c->req->body, 'UTF-8', $flavour );
761 elsif ( $content_type =~ m/application\/marc-in-json/ ) {
762 $record = MARC::Record->new_from_mij_structure( $c->req->json );
764 elsif ( $content_type =~ m/application\/marc/ ) {
765 $record = MARC::Record->new_from_usmarc( $c->req->body );
772 "application/marcxml+xml",
773 "application/marc-in-json",
779 ModBiblio( $record, $biblio_id, $frameworkcode );
783 openapi => { id => $biblio_id }
787 $c->unhandled_exception($_);
793 Controller function that handles retrieving a single biblio object
798 my $c = shift->openapi->valid_input or return;
802 { prefetch => ['metadata'] } # don't prefetch metadata if not needed
803 unless $c->req->headers->accept =~ m/application\/json/;
805 my $biblios = $c->objects->search_rs( Koha::Biblios->new );
809 if ( $c->req->headers->accept =~ m/application\/json(;.*)?$/ ) {
812 json => $c->objects->to_api( $biblios ),
816 $c->req->headers->accept =~ m/application\/marcxml\+xml(;.*)?$/ )
818 $c->res->headers->add( 'Content-Type', 'application/marcxml+xml' );
821 text => $biblios->print_collection('marcxml')
825 $c->req->headers->accept =~ m/application\/marc-in-json(;.*)?$/ )
827 $c->res->headers->add( 'Content-Type', 'application/marc-in-json' );
830 data => $biblios->print_collection('mij')
833 elsif ( $c->req->headers->accept =~ m/application\/marc(;.*)?$/ ) {
834 $c->res->headers->add( 'Content-Type', 'application/marc' );
837 text => $biblios->print_collection('marc')
840 elsif ( $c->req->headers->accept =~ m/text\/plain(;.*)?$/ ) {
843 text => $biblios->print_collection('txt')
850 "application/json", "application/marcxml+xml",
851 "application/marc-in-json", "application/marc",
858 $c->unhandled_exception($_);